diff --git a/.travis.yml b/.travis.yml index 62330af82..357c9e593 100644 --- a/.travis.yml +++ b/.travis.yml @@ -13,6 +13,12 @@ matrix: packages: ['g++-4.9', 'valgrind', 'python-pip', 'python-yaml'] before_script: - pip install --user git+git://github.com/eddyxu/cpp-coveralls.git + after_success: + - make clean + - touch src/json.hpp + - make json_unit CXXFLAGS="-fprofile-arcs -ftest-coverage -std=c++11 -lstdc++" CXX=$COMPILER + - ./json_unit "*" + - coveralls --exclude test/catch.hpp --exclude test/unit.cpp --include src/json.hpp --gcov-options '\-lp' --gcov 'gcov-4.9' env: COMPILER=g++-4.9 - os: linux @@ -31,14 +37,22 @@ matrix: packages: ['clang-3.6', 'valgrind'] env: COMPILER=clang++-3.6 + - os: linux + compiler: clang + addons: + apt: + sources: ['ubuntu-toolchain-r-test', 'llvm-toolchain-precise-3.7'] + packages: ['clang-3.7', 'valgrind'] + env: COMPILER=clang++-3.7 + + # - os: osx + # compiler: clang + # env: COMPILER=clang + # before_install: + # - brew update + # - brew install valgrind + script: - make CXX=$COMPILER CXXFLAGS="-lstdc++" - ./json_unit "*" - valgrind --error-exitcode=1 --leak-check=full ./json_unit - -after_success: - - if [ "$COMPILER" = "g++-4.9" ]; then make clean ; fi - - if [ "$COMPILER" = "g++-4.9" ]; then touch src/json.hpp ; fi - - if [ "$COMPILER" = "g++-4.9" ]; then make json_unit CXXFLAGS="-fprofile-arcs -ftest-coverage -std=c++11 -lstdc++" CXX=$COMPILER ; fi - - if [ "$COMPILER" = "g++-4.9" ]; then ./json_unit "*" ; fi - - if [ "$COMPILER" = "g++-4.9" ]; then coveralls --exclude test/catch.hpp --exclude test/unit.cpp --include src/json.hpp --gcov-options '\-lp' --gcov 'gcov-4.9' ; fi diff --git a/CMakeLists.txt b/CMakeLists.txt index a1fb47e8f..16e9f960e 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -16,7 +16,7 @@ if(MSVC) add_definitions(-D_SCL_SECURE_NO_WARNINGS) else(MSVC) set(CMAKE_CXX_FLAGS - "-std=c++11 -stdlib=libc++" + "-std=c++11" ) endif(MSVC) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 000000000..81fac6d94 --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1,69 @@ +# How to contribute + +This project started as a little excuse to exercise some of the cool new C++11 features. Over time, people actually started to use the JSON library (yey!) and started to help improve it by proposing features, finding bugs, or even fixing my mistakes. I am really [thankful](https://github.com/nlohmann/json/blob/master/README.md#thanks) for this and try to keep track of all the helpers. + +To make it as easy as possible for you to contribute and for me to keep an overview, here are a few guidelines which should help us avoid all kinds of unnecessary work or disappointment. And of course, this document is subject to discussion, so please [create an issue](https://github.com/nlohmann/json/issues/new) or a pull request if you find a way to improve it! + +## Prerequisites + +Please [create an issue](https://github.com/nlohmann/json/issues/new), assuming one does not already exist, and describe your concern. Note you need a [GitHub account](https://github.com/signup/free) for this. + +If you want propose changes to the code, you need to download the [`re2c`](http://re2c.org) tool. + +## Describe your issue + +Clearly describe the issue: + +- If it is a bug, please describe how to **reproduce** it. If possible, attach a complete example which demonstrates the error. Please also state what you **expected** to happen instead of the error. +- If you propose a change or addition, try to give an **example** how the improved code could look like or how to use it. +- If you found a compilation error, please tell us which **compiler** (version and operating system) you used and paste the (relevant part of) the error messages to the ticket. + +## Files to change + +There are currently two files which need to be edited: + +1. [`src/json.hpp.re2c`](https://github.com/nlohmann/json/blob/master/src/json.hpp.re2c) (note the `.re2c` suffix) - This file contains a comment section which describes the JSON lexic. This section is translated by [`re2c`](http://re2c.org) into file [`src/json.hpp`](https://github.com/nlohmann/json/blob/master/src/json.hpp) which is plain "vanilla" C++11 code. (In fact, the generated lexer consists of some hundred lines of `goto`s, which is a hint you never want to edit this file...). + + If you only edit file `src/json.hpp` (without the `.re2c` suffix), your changes will be overwritten as soon as the lexer is touched again. To generate the `src/json.hpp` file which is actually used during compilation of the tests and all other code, please execute + + ```sh + make re2c + ``` + + To run [`re2c`](http://re2c.org) and generate/overwrite file `src/json.hpp` with your changes in file `src/json.hpp.re2c`. + + +2. [`test/unit.cpp`](https://github.com/nlohmann/json/blob/master/test/unit.cpp) - This contains the [Catch](https://github.com/philsquared/Catch) unit tests which currently cover [100 %](https://coveralls.io/github/nlohmann/json) of the library's code. + + If you add or change a feature, please also add a unit test to this file. The unit tests can be compiled with + + ```sh + make + ``` + + and can be executed with + + ```sh + ./json_unit + ``` + + The test cases are also executed with several different compilers on [Travis](https://travis-ci.org/nlohmann/json) once you open a pull request. + +Please understand that I cannot accept pull requests changing only file `src/json.hpp`. + + +## Please don't + +- Only make changes to file `src/json.hpp` -- please read the paragraph above and understand why `src/json.hpp.re2c` exists. +- The C++11 support varies between different **compilers** and versions. Please note the [list of supported compilers](https://github.com/nlohmann/json/blob/master/README.md#supported-compilers). Some compilers like GCC 4.8 (and earlier), Clang 3.3 (and earlier), or Microsoft Visual Studio 13.0 and earlier are known not to work due to missing or incomplete C++11 support. Please refrain from proposing changes that work around these compiler's limitations with `#ifdef`s or other means. +- Specifically, I am aware of compilation problems with **Microsoft Visual Studio** (there even is an [issue label](https://github.com/nlohmann/json/issues?utf8=✓&q=label%3A%22visual+studio%22+) for these kind of bugs). I understand that even in 2016, complete C++11 support isn't there yet. But please also understand that I do not want to drop features or uglify the code just to make Microsoft's sub-standard compiler happy. The past has shown that there are ways to express the functionality such that the code compiles with the most recent MSVC - unfortunately, this is not the main objective of the project. +- Please refrain from proposing changes that would **break [JSON](http://json.org) conformance**. If you propose a conformant extension of JSON to be supported by the library, please motivate this extension. + +## Wanted + +The following areas really need contribution: + +- Getting the code to compile without errors with the latest **Microsoft Visual Studio** version. I am not using Windows, so I cannot debug code with MSVC myself. There is a job on [AppVeyor](https://ci.appveyor.com/project/nlohmann/json) though. +- Extending the **continuous integration** beyond Linux running some versions of GCC and Clang on [Travis](https://travis-ci.org/nlohmann/json) and Microsoft Visual Studio on [AppVeyor](https://ci.appveyor.com/project/nlohmann/json). We have found a lot of bugs just because several compilers behave in a slightly different manner. +- Improving the efficiency of the **JSON parser**. The current parser is implemented as a naive recursive descent parser with hand coded string handling. More sophisticated approaches like LALR parsers would be really appreciated. That said, parser generators like Bison or ANTLR do not play nice with single-header files -- I really would like to keep the parser inside the `json.hpp` header, and I am not aware of approaches similar to [`re2c`](http://re2c.org) for parsing. +- Extending and updating existing **benchmarks** to include (the most recent version of) this library. Though efficiency is not everything, speed and memory consumption are very important characteristics for C++ developers, so having proper comparisons would be interesting. diff --git a/LICENSE.MIT b/LICENSE.MIT index 18332db3c..e2ac4891d 100644 --- a/LICENSE.MIT +++ b/LICENSE.MIT @@ -1,7 +1,7 @@ The library is licensed under the MIT License : -Copyright (c) 2013-2015 Niels Lohmann +Copyright (c) 2013-2016 Niels Lohmann 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 diff --git a/README.md b/README.md index d27b78a7c..95e920f1b 100644 --- a/README.md +++ b/README.md @@ -40,14 +40,17 @@ to the files you want to use JSON objects. That's it. Do not forget to set the n ## Supported compilers -Though it's 2015 already, the support for C++11 is still a bit sparse. Currently, the following compilers are known to work: +Though it's 2016 already, the support for C++11 is still a bit sparse. Currently, the following compilers are known to work: -- GCC 4.8 - 5.2 -- Clang 3.4 - 3.7 -- Microsoft Visual C++ 14.0 RC +- GCC 4.9 - 6.0 (and possibly later) +- Clang 3.4 - 3.8 (and possibly later) +- Microsoft Visual C++ 14.0 RC (and possibly later) I would be happy to learn about other compilers/versions. +For GCC running on MinGW or Android SDK, the error `'to_string' is not a member of 'std'` (or similarly, for `strtod`) may occur. Note this is not an issue with the code, but rather with the compiler itself. Please refer to [this site](http://tehsausage.com/mingw-to-string) and [this discussion](https://github.com/nlohmann/json/issues/136) for information on how to fix this bug. + + ## Examples Here are some examples to give you an idea how to use the class. @@ -348,7 +351,7 @@ int vi = jn.get(); The class is licensed under the [MIT License](http://opensource.org/licenses/MIT): -Copyright © 2013-2015 [Niels Lohmann](http://nlohmann.me) +Copyright © 2013-2016 [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: @@ -372,16 +375,26 @@ I deeply appreciate the help of the following people. - [Eric Cornelius](https://github.com/EricMCornelius) pointed out a bug in the handling with NaN and infinity values. He also improved the performance of the string escaping. - [易思龙](https://github.com/likebeta) implemented a conversion from anonymous enums. - [kepkin](https://github.com/kepkin) patiently pushed forward the support for Microsoft Visual studio. -- [gregmarr](https://github.com/gregmarr) simplified the implementation of reverse iterators. +- [gregmarr](https://github.com/gregmarr) simplified the implementation of reverse iterators and helped with numerous hints and improvements. - [Caio Luppi](https://github.com/caiovlp) fixed a bug in the Unicode handling. - [dariomt](https://github.com/dariomt) fixed some typos in the examples. - [Daniel Frey](https://github.com/d-frey) cleaned up some pointers and implemented exception-safe memory allocation. - [Colin Hirsch](https://github.com/ColinH) took care of a small namespace issue. - [Huu Nguyen](https://github.com/whoshuu) correct a variable name in the documentation. - [Silverweed](https://github.com/silverweed) overloaded `parse()` to accept an rvalue reference. +- [dariomt](https://github.com/dariomt) fixed a subtlety in MSVC type support. +- [ZahlGraf](https://github.com/ZahlGraf) added a workaround that allows compilation using Android NDK. +- [whackashoe](https://github.com/whackashoe) replaced a function that was marked as unsafe by Visual Studio. +- [406345](https://github.com/406345) fixed two small warnings. +- [Glen Fernandes](https://github.com/glenfe) noted a potential portability problem in the `has_mapped_type` function. +- [Corbin Hughes](https://github.com/nibroc) fixed some typos in the contribution guidelines. Thanks a lot for helping out! +## Notes + +- The code contains numerous debug assertions which can be switched off by defining the preprocessor macro `NDEBUG`, see the [documentation of `assert`](http://en.cppreference.com/w/cpp/error/assert). + ## Execute unit tests To compile and run the tests, you need to execute @@ -391,7 +404,7 @@ $ make $ ./json_unit "*" =============================================================================== -All tests passed (3341774 assertions in 27 test cases) +All tests passed (3343239 assertions in 28 test cases) ``` For more information, have a look at the file [.travis.yml](https://github.com/nlohmann/json/blob/master/.travis.yml). diff --git a/doc/ChangeLog.md b/doc/ChangeLog.md index d6afa4f16..a43b0e1ab 100644 --- a/doc/ChangeLog.md +++ b/doc/ChangeLog.md @@ -1,5 +1,27 @@ # JSON for Modern C++ +## Version 1.0.0 + +- Release date: 2015-12-28 +- MD5: 331c30e4407ecdcf591e9e3ed85100d0 + +### Summary + +This is the first official release. Compared to the [prerelease version 1.0.0-rc1](https://github.com/nlohmann/json/releases/tag/v1.0.0-rc1), only a few minor improvements have been made: + +### Changes + +- *Changed*: A **UTF-8 byte order mark** is silently ignored. +- *Changed*: `sprintf` is no longer used. +- *Changed*: `iterator_wrapper` also works for const objects; note: the name may change! +- *Changed*: **Error messages** during deserialization have been improved. +- *Added*: The `parse` function now also works with type `std::istream&&`. +- *Added*: Function `value(key, default_value)` returns either a copy of an object's element at the specified key or a given default value if no element with the key exists. +- *Added*: Public functions are tagged with the version they were introduced. This shall allow for better **versioning** in the future. +- *Added*: All public functions and types are **documented** (see http://nlohmann.github.io/json/) including executable examples. +- *Added*: Allocation of all types (in particular arrays, strings, and objects) is now exception-safe. +- *Added*: They descriptions of thrown exceptions have been overworked and are part of the tests suite and documentation. + ## Version 1.0.0-rc1 - Release date: 2015-07-26 diff --git a/doc/Doxyfile b/doc/Doxyfile index b03aa7913..9cca8a834 100644 --- a/doc/Doxyfile +++ b/doc/Doxyfile @@ -5,7 +5,7 @@ #--------------------------------------------------------------------------- DOXYFILE_ENCODING = UTF-8 PROJECT_NAME = "JSON for Modern C++" -PROJECT_NUMBER = 1.0.0-rc1 +PROJECT_NUMBER = 1.0.0 PROJECT_BRIEF = PROJECT_LOGO = OUTPUT_DIRECTORY = . @@ -121,7 +121,7 @@ USE_MDFILE_AS_MAINPAGE = #--------------------------------------------------------------------------- # Configuration options related to source browsing #--------------------------------------------------------------------------- -SOURCE_BROWSER = NO +SOURCE_BROWSER = YES INLINE_SOURCES = NO STRIP_CODE_COMMENTS = YES REFERENCED_BY_RELATION = NO diff --git a/doc/examples/at__object_t_key_type.cpp b/doc/examples/at__object_t_key_type.cpp index 85de56565..29c24c0ce 100644 --- a/doc/examples/at__object_t_key_type.cpp +++ b/doc/examples/at__object_t_key_type.cpp @@ -26,8 +26,8 @@ int main() { object.at("the fast") = "il rapido"; } - catch (std::out_of_range) + catch (std::out_of_range& e) { - std::cout << "out of range" << '\n'; + std::cout << "out of range: " << e.what() << '\n'; } } diff --git a/doc/examples/at__object_t_key_type.link b/doc/examples/at__object_t_key_type.link index 1f4956378..beba6a0fb 100644 --- a/doc/examples/at__object_t_key_type.link +++ b/doc/examples/at__object_t_key_type.link @@ -1 +1 @@ -online \ No newline at end of file +online \ No newline at end of file diff --git a/doc/examples/at__object_t_key_type.output b/doc/examples/at__object_t_key_type.output index 90ccb1cdb..79cff2d7f 100644 --- a/doc/examples/at__object_t_key_type.output +++ b/doc/examples/at__object_t_key_type.output @@ -1,3 +1,3 @@ "il brutto" {"the bad":"il cattivo","the good":"il buono","the ugly":"il brutto"} -out of range +out of range: key 'the fast' not found diff --git a/doc/examples/at__size_type.cpp b/doc/examples/at__size_type.cpp index 5195005ee..617485bf0 100644 --- a/doc/examples/at__size_type.cpp +++ b/doc/examples/at__size_type.cpp @@ -21,8 +21,8 @@ int main() { array.at(5) = "sixth"; } - catch (std::out_of_range) + catch (std::out_of_range& e) { - std::cout << "out of range" << '\n'; + std::cout << "out of range: " << e.what() << '\n'; } } diff --git a/doc/examples/at__size_type.link b/doc/examples/at__size_type.link index 49830f7b0..a5ce10e4d 100644 --- a/doc/examples/at__size_type.link +++ b/doc/examples/at__size_type.link @@ -1 +1 @@ -online \ No newline at end of file +online \ No newline at end of file diff --git a/doc/examples/at__size_type.output b/doc/examples/at__size_type.output index 93748f7f3..d1f68bdb8 100644 --- a/doc/examples/at__size_type.output +++ b/doc/examples/at__size_type.output @@ -1,3 +1,3 @@ "third" ["first","second","third","fourth"] -out of range +out of range: array index 5 is out of range diff --git a/doc/examples/basic_json__value.cpp b/doc/examples/basic_json__value.cpp new file mode 100644 index 000000000..37081791e --- /dev/null +++ b/doc/examples/basic_json__value.cpp @@ -0,0 +1,29 @@ +#include + +using namespace nlohmann; + +int main() +{ + // create a JSON object with different entry types + json j = + { + {"integer", 1}, + {"floating", 42.23}, + {"string", "hello world"}, + {"boolean", true}, + {"object", {{"key1", 1}, {"key2", 2}}}, + {"array", {1, 2, 3}} + }; + + // access existing values + int v_integer = j.value("integer", 0); + double v_floating = j.value("floating", 47.11); + + // access nonexisting values and rely on default value + std::string v_string = j.value("nonexisting", "oops"); + bool v_boolean = j.value("nonexisting", false); + + // output values + std::cout << std::boolalpha << v_integer << " " << v_floating + << " " << v_string << " " << v_boolean << "\n"; +} diff --git a/doc/examples/basic_json__value.link b/doc/examples/basic_json__value.link new file mode 100644 index 000000000..6698c8917 --- /dev/null +++ b/doc/examples/basic_json__value.link @@ -0,0 +1 @@ +online \ No newline at end of file diff --git a/doc/examples/basic_json__value.output b/doc/examples/basic_json__value.output new file mode 100644 index 000000000..dfc40e58c --- /dev/null +++ b/doc/examples/basic_json__value.output @@ -0,0 +1 @@ +1 42.23 oops false diff --git a/doc/examples/operatorarray__key_type_const.cpp b/doc/examples/operatorarray__key_type_const.cpp index 6d0c91412..a59cca58b 100644 --- a/doc/examples/operatorarray__key_type_const.cpp +++ b/doc/examples/operatorarray__key_type_const.cpp @@ -5,7 +5,7 @@ using namespace nlohmann; int main() { // create a JSON object - json object = + const json object = { {"one", 1}, {"two", 2}, {"three", 2.9} }; diff --git a/doc/examples/operatorarray__key_type_const.link b/doc/examples/operatorarray__key_type_const.link index 0f03498a9..7e740a1b8 100644 --- a/doc/examples/operatorarray__key_type_const.link +++ b/doc/examples/operatorarray__key_type_const.link @@ -1 +1 @@ -online \ No newline at end of file +online \ No newline at end of file diff --git a/src/json.hpp b/src/json.hpp index 30e6b2fe3..db3d13aa6 100644 --- a/src/json.hpp +++ b/src/json.hpp @@ -7,32 +7,32 @@ header-only JSON class. Class @ref nlohmann::basic_json is a good entry point for the documentation. @copyright The code is licensed under the [MIT - License](http://opensource.org/licenses/MIT): -
- Copyright © 2013-2015 Niels Lohmann. -
- 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. + License](http://opensource.org/licenses/MIT): +
+ Copyright © 2013-2016 Niels Lohmann. +
+ 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. @author [Niels Lohmann](http://nlohmann.me) @see https://github.com/nlohmann/json to download the source code + +@version 1.0.0 */ #ifndef NLOHMANN_JSON_HPP @@ -40,6 +40,7 @@ Class @ref nlohmann::basic_json is a good entry point for the documentation. #include #include +#include #include #include #include @@ -73,6 +74,7 @@ Class @ref nlohmann::basic_json is a good entry point for the documentation. /*! @brief namespace for Niels Lohmann @see https://github.com/nlohmann +@since version 1.0.0 */ namespace nlohmann { @@ -80,6 +82,7 @@ namespace nlohmann /*! @brief unnamed namespace with internal helper functions +@since version 1.0.0 */ namespace { @@ -92,9 +95,9 @@ struct has_mapped_type { private: template static char test(typename C::mapped_type*); - template static int test(...); + template static char (&test(...))[2]; public: - enum { value = sizeof(test(0)) == sizeof(char) }; + static constexpr bool value = sizeof(test(0)) == 1; }; /// "equality" comparison for floating point numbers @@ -130,7 +133,7 @@ default) - [MoveConstructible](http://en.cppreference.com/w/cpp/concept/MoveConstructible): A JSON value can be constructed from an rvalue argument. - [CopyConstructible](http://en.cppreference.com/w/cpp/concept/CopyConstructible): - A JSON value can be copy-constrcuted from an lvalue expression. + A JSON value can be copy-constructed from an lvalue expression. - [MoveAssignable](http://en.cppreference.com/w/cpp/concept/MoveAssignable): A JSON value van be assigned from an rvalue argument. - [CopyAssignable](http://en.cppreference.com/w/cpp/concept/CopyAssignable): @@ -168,6 +171,10 @@ default) @endinternal @see RFC 7159 + +@since version 1.0.0 + +@nosubgrouping */ template < template class ObjectType = std::map, @@ -261,10 +268,16 @@ class basic_json > where a name is a string and a value is a string, number, boolean, null, > object, or array. - To store objects in C++, a type is defined by the template parameters @a - ObjectType which chooses the container (e.g., `std::map` or - `std::unordered_map`), @a StringType which chooses the type of the keys or - names, and @a AllocatorType which chooses the allocator to use. + To store objects in C++, a type is defined by the template parameters + described below. + + @tparam ObjectType the container to store objects (e.g., `std::map` or + `std::unordered_map`) + @tparam StringType the type of the keys or names (e.g., `std::string`). The + comparison function `std::less` is used to order elements + inside the container. + @tparam AllocatorType the allocator to use for objects (e.g., + `std::allocator`) #### Default type @@ -314,10 +327,12 @@ class basic_json #### Storage - Objects are stored as pointers in a `basic_json` type. That is, for any + Objects are stored as pointers in a @ref basic_json type. That is, for any access to object values, a pointer of type `object_t*` must be dereferenced. - @sa array_t + @sa @ref array_t -- type for an array value + + @since version 1.0.0 */ using object_t = ObjectType An array is an ordered sequence of zero or more values. - To store objects in C++, a type is defined by the template parameters @a - ArrayType which chooses the container (e.g., `std::vector` or `std::list`) - and @a AllocatorType which chooses the allocator to use. + To store objects in C++, a type is defined by the template parameters + explained below. + + @tparam ArrayType container type to store arrays (e.g., `std::vector` or + `std::list`) + @tparam AllocatorType allocator to use for arrays (e.g., `std::allocator`) #### Default type @@ -359,8 +377,12 @@ class basic_json #### Storage - Arrays are stored as pointers in a `basic_json` type. That is, for any + Arrays are stored as pointers in a @ref basic_json type. That is, for any access to array values, a pointer of type `array_t*` must be dereferenced. + + @sa @ref object_t -- type for an object value + + @since version 1.0.0 */ using array_t = ArrayType>; @@ -370,11 +392,12 @@ class basic_json [RFC 7159](http://rfc7159.net/rfc7159) describes JSON strings as follows: > A string is a sequence of zero or more Unicode characters. - To store objects in C++, a type is defined by the template parameters @a - StringType which chooses the container (e.g., `std::string`) to use. + To store objects in C++, a type is defined by the template parameter + described below. Unicode values are split by the JSON class into byte-sized + characters during deserialization. - Unicode values are split by the JSON class into byte-sized characters - during deserialization. + @tparam StringType the container to store strings (e.g., `std::string`). + Note this container is used for keys/names in objects, see @ref object_t. #### Default type @@ -402,9 +425,11 @@ class basic_json #### Storage - String values are stored as pointers in a `basic_json` type. That is, for - any access to string values, a pointer of type `string_t*` must be + String values are stored as pointers in a @ref basic_json type. That is, + for any access to string values, a pointer of type `string_t*` must be dereferenced. + + @since version 1.0.0 */ using string_t = StringType; @@ -428,7 +453,9 @@ class basic_json #### Storage - Boolean values are stored directly inside a `basic_json` type. + Boolean values are stored directly inside a @ref basic_json type. + + @since version 1.0.0 */ using boolean_t = BooleanType; @@ -492,7 +519,11 @@ class basic_json #### Storage - Integer number values are stored directly inside a `basic_json` type. + Integer number values are stored directly inside a @ref basic_json type. + + @sa @ref number_float_t -- type for number values (floating-point) + + @since version 1.0.0 */ using number_integer_t = NumberIntegerType; @@ -547,12 +578,17 @@ class basic_json This implementation does exactly follow this approach, as it uses double precision floating-point numbers. Note values smaller than - `-1.79769313486232e+308` and values greather than `1.79769313486232e+308` + `-1.79769313486232e+308` and values greater than `1.79769313486232e+308` will be stored as NaN internally and be serialized to `null`. #### Storage - Floating-point number values are stored directly inside a `basic_json` type. + Floating-point number values are stored directly inside a @ref basic_json + type. + + @sa @ref number_integer_t -- type for number values (integer) + + @since version 1.0.0 */ using number_float_t = NumberFloatType; @@ -567,8 +603,11 @@ class basic_json @brief the JSON type enumeration This enumeration collects the different JSON types. It is internally used - to distinguish the stored values, and the functions is_null, is_object, - is_array, is_string, is_boolean, is_number, and is_discarded rely on it. + to distinguish the stored values, and the functions @ref is_null(), @ref + is_object(), @ref is_array(), @ref is_string(), @ref is_boolean(), @ref + is_number(), and @ref is_discarded() rely on it. + + @since version 1.0.0 */ enum class value_t : uint8_t { @@ -586,7 +625,7 @@ class basic_json private: /// helper for exception-safe object creation template - static T* create( Args&& ... args ) + static T* create(Args&& ... args) { AllocatorType alloc; auto deleter = [&](T * object) @@ -602,7 +641,13 @@ class basic_json // JSON value storage // //////////////////////// - /// a JSON value + /*! + @brief a JSON value + + The actual storage for a JSON value of the @ref basic_json class. + + @since version 1.0.0 + */ union json_value { /// object (stored with pointer to save storage) @@ -631,47 +676,46 @@ class basic_json { switch (t) { - case (value_t::null): - case (value_t::discarded): - { - break; - } - - case (value_t::object): + case value_t::object: { object = create(); break; } - case (value_t::array): + case value_t::array: { array = create(); break; } - case (value_t::string): + case value_t::string: { string = create(""); break; } - case (value_t::boolean): + case value_t::boolean: { boolean = boolean_t(false); break; } - case (value_t::number_integer): + case value_t::number_integer: { number_integer = number_integer_t(0); break; } - case (value_t::number_float): + case value_t::number_float: { number_float = number_float_t(0.0); break; } + + default: + { + break; + } } } @@ -705,6 +749,8 @@ class basic_json This enumeration lists the parser events that can trigger calling a callback function of type @ref parser_callback_t during parsing. + + @since version 1.0.0 */ enum class parse_event_t : uint8_t { @@ -746,17 +792,17 @@ class basic_json parse_event_t::array_end | the parser read `]` and finished processing a JSON array | depth of the parent of the JSON array | the parsed JSON array parse_event_t::value | the parser finished reading a JSON value | depth of the value | the parsed JSON value - Discarding a value (i.e., returning `false`) has different effects depending on the - context in which function was called: + Discarding a value (i.e., returning `false`) has different effects + depending on the context in which function was called: - Discarded values in structured types are skipped. That is, the parser will behave as if the discarded value was never read. - In case a value outside a structured type is skipped, it is replaced with `null`. This case happens if the top-level element is skipped. - @param[in] depth the depth of the recursion during parsing + @param[in] depth the depth of the recursion during parsing - @param[in] event an event of type parse_event_t indicating the context in + @param[in] event an event of type parse_event_t indicating the context in the callback function has been called @param[in,out] parsed the current intermediate parse result; note that @@ -768,15 +814,19 @@ class basic_json @sa @ref parse(std::istream&, parser_callback_t) or @ref parse(const string_t&, parser_callback_t) for examples + + @since version 1.0.0 */ - using parser_callback_t = std::function; + using parser_callback_t = std::function; ////////////////// // constructors // ////////////////// + /// @name constructors and destructors + /// @{ + /*! @brief create an empty value with a given type @@ -792,7 +842,7 @@ class basic_json object | `{}` array | `[]` - @param[in] value the type of the value to create + @param[in] value_type the type of the value to create @complexity Constant. @@ -801,9 +851,21 @@ class basic_json @liveexample{The following code shows the constructor for different @ref value_t values,basic_json__value_t} + + @sa @ref basic_json(std::nullptr_t) -- create a `null` value + @sa @ref basic_json(boolean_t value) -- create a boolean value + @sa @ref basic_json(const string_t&) -- create a string value + @sa @ref basic_json(const object_t&) -- create a object value + @sa @ref basic_json(const array_t&) -- create a array value + @sa @ref basic_json(const number_float_t) -- create a number + (floating-point) value + @sa @ref basic_json(const number_integer_t) -- create a number (integer) + value + + @since version 1.0.0 */ - basic_json(const value_t value) - : m_type(value), m_value(value) + basic_json(const value_t value_type) + : m_type(value_type), m_value(value_type) {} /*! @@ -821,7 +883,9 @@ class basic_json @liveexample{The following code shows the constructor for a `null` JSON value.,basic_json} - @sa basic_json(std::nullptr_t) + @sa @ref basic_json(std::nullptr_t) -- create a `null` value + + @since version 1.0.0 */ basic_json() noexcept = default; @@ -831,7 +895,7 @@ class basic_json Create a `null` JSON value. This is the explicitly version of the `null` value constructor as it takes a null pointer as parameter. It allows to create `null` values by explicitly assigning a @c nullptr to a JSON value. - The passed null pointer itself is not read - it is only used to choose the + The passed null pointer itself is not read -- it is only used to choose the right constructor. @complexity Constant. @@ -839,7 +903,10 @@ class basic_json @liveexample{The following code shows the constructor with null pointer parameter.,basic_json__nullptr_t} - @sa basic_json() + @sa @ref basic_json() -- default constructor (implicitly creating a `null` + value) + + @since version 1.0.0 */ basic_json(std::nullptr_t) noexcept : basic_json(value_t::null) @@ -850,19 +917,22 @@ class basic_json Create an object JSON value with a given content. - @param[in] value a value for the object + @param[in] val a value for the object - @complexity Linear in the size of the passed @a value. + @complexity Linear in the size of the passed @a val. @throw std::bad_alloc if allocation for object value fails @liveexample{The following code shows the constructor with an @ref object_t parameter.,basic_json__object_t} - @sa basic_json(const CompatibleObjectType&) + @sa @ref basic_json(const CompatibleObjectType&) -- create an object value + from a compatible STL container + + @since version 1.0.0 */ - basic_json(const object_t& value) - : m_type(value_t::object), m_value(value) + basic_json(const object_t& val) + : m_type(value_t::object), m_value(val) {} /*! @@ -875,28 +945,30 @@ class basic_json @tparam CompatibleObjectType an object type whose `key_type` and `value_type` is compatible to @ref object_t - @param[in] value a value for the object + @param[in] val a value for the object - @complexity Linear in the size of the passed @a value. + @complexity Linear in the size of the passed @a val. @throw std::bad_alloc if allocation for object value fails @liveexample{The following code shows the constructor with several compatible object type parameters.,basic_json__CompatibleObjectType} - @sa basic_json(const object_t&) + @sa @ref basic_json(const object_t&) -- create an object value + + @since version 1.0.0 */ template ::value and std::is_constructible::value, int>::type = 0> - basic_json(const CompatibleObjectType& value) + basic_json(const CompatibleObjectType& val) : m_type(value_t::object) { using std::begin; using std::end; - m_value.object = create(begin(value), end(value)); + m_value.object = create(begin(val), end(val)); } /*! @@ -904,19 +976,22 @@ class basic_json Create an array JSON value with a given content. - @param[in] value a value for the array + @param[in] val a value for the array - @complexity Linear in the size of the passed @a value. + @complexity Linear in the size of the passed @a val. @throw std::bad_alloc if allocation for array value fails @liveexample{The following code shows the constructor with an @ref array_t parameter.,basic_json__array_t} - @sa basic_json(const CompatibleArrayType&) + @sa @ref basic_json(const CompatibleArrayType&) -- create an array value + from a compatible STL containers + + @since version 1.0.0 */ - basic_json(const array_t& value) - : m_type(value_t::array), m_value(value) + basic_json(const array_t& val) + : m_type(value_t::array), m_value(val) {} /*! @@ -929,16 +1004,18 @@ class basic_json @tparam CompatibleArrayType an object type whose `value_type` is compatible to @ref array_t - @param[in] value a value for the array + @param[in] val a value for the array - @complexity Linear in the size of the passed @a value. + @complexity Linear in the size of the passed @a val. @throw std::bad_alloc if allocation for array value fails @liveexample{The following code shows the constructor with several compatible array type parameters.,basic_json__CompatibleArrayType} - @sa basic_json(const array_t&) + @sa @ref basic_json(const array_t&) -- create an array value + + @since version 1.0.0 */ template ::value and std::is_constructible::value, int>::type = 0> - basic_json(const CompatibleArrayType& value) + basic_json(const CompatibleArrayType& val) : m_type(value_t::array) { using std::begin; using std::end; - m_value.array = create(begin(value), end(value)); + m_value.array = create(begin(val), end(val)); } /*! @@ -963,20 +1040,24 @@ class basic_json Create an string JSON value with a given content. - @param[in] value a value for the string + @param[in] val a value for the string - @complexity Linear in the size of the passed @a value. + @complexity Linear in the size of the passed @a val. @throw std::bad_alloc if allocation for string value fails @liveexample{The following code shows the constructor with an @ref string_t parameter.,basic_json__string_t} - @sa basic_json(const typename string_t::value_type*) - @sa basic_json(const CompatibleStringType&) + @sa @ref basic_json(const typename string_t::value_type*) -- create a + string value from a character pointer + @sa @ref basic_json(const CompatibleStringType&) -- create a string value + from a compatible string container + + @since version 1.0.0 */ - basic_json(const string_t& value) - : m_type(value_t::string), m_value(value) + basic_json(const string_t& val) + : m_type(value_t::string), m_value(val) {} /*! @@ -984,20 +1065,23 @@ class basic_json Create a string JSON value with a given content. - @param[in] value a literal value for the string + @param[in] val a literal value for the string - @complexity Linear in the size of the passed @a value. + @complexity Linear in the size of the passed @a val. @throw std::bad_alloc if allocation for string value fails @liveexample{The following code shows the constructor with string literal parameter.,basic_json__string_t_value_type} - @sa basic_json(const string_t&) - @sa basic_json(const CompatibleStringType&) + @sa @ref basic_json(const string_t&) -- create a string value + @sa @ref basic_json(const CompatibleStringType&) -- create a string value + from a compatible string container + + @since version 1.0.0 */ - basic_json(const typename string_t::value_type* value) - : basic_json(string_t(value)) + basic_json(const typename string_t::value_type* val) + : basic_json(string_t(val)) {} /*! @@ -1005,26 +1089,30 @@ class basic_json Create a string JSON value with a given content. - @param[in] value a value for the string + @param[in] val a value for the string @tparam CompatibleStringType an string type which is compatible to @ref string_t - @complexity Linear in the size of the passed @a value. + @complexity Linear in the size of the passed @a val. @throw std::bad_alloc if allocation for string value fails @liveexample{The following code shows the construction of a string value from a compatible type.,basic_json__CompatibleStringType} - @sa basic_json(const string_t&) + @sa @ref basic_json(const string_t&) -- create a string value + @sa @ref basic_json(const typename string_t::value_type*) -- create a + string value from a character pointer + + @since version 1.0.0 */ template ::value, int>::type = 0> - basic_json(const CompatibleStringType& value) - : basic_json(string_t(value)) + basic_json(const CompatibleStringType& val) + : basic_json(string_t(val)) {} /*! @@ -1032,26 +1120,28 @@ class basic_json Creates a JSON boolean type from a given value. - @param[in] value a boolean value to store + @param[in] val a boolean value to store @complexity Constant. @liveexample{The example below demonstrates boolean values.,basic_json__boolean_t} + + @since version 1.0.0 */ - basic_json(boolean_t value) - : m_type(value_t::boolean), m_value(value) + basic_json(boolean_t val) + : m_type(value_t::boolean), m_value(val) {} /*! @brief create an integer number (explicit) - Create an interger number JSON value with a given content. + Create an integer number JSON value with a given content. @tparam T helper type to compare number_integer_t and int (not visible in) the interface. - @param[in] value an integer to create a JSON number from + @param[in] val an integer to create a JSON number from @note This constructor would have the same signature as @ref basic_json(const int value), so we need to switch this one off in case @@ -1062,15 +1152,19 @@ class basic_json @liveexample{The example below shows the construction of a JSON integer number value.,basic_json__number_integer_t} - @sa basic_json(const int) + @sa @ref basic_json(const int) -- create a number value (integer) + @sa @ref basic_json(const CompatibleNumberIntegerType) -- create a number + value (integer) from a compatible number type + + @since version 1.0.0 */ template::value) and std::is_same::value , int>::type = 0> - basic_json(const number_integer_t value) - : m_type(value_t::number_integer), m_value(value) + basic_json(const number_integer_t val) + : m_type(value_t::number_integer), m_value(val) {} /*! @@ -1078,7 +1172,7 @@ class basic_json Create an integer number JSON value with a given content. - @param[in] value an integer to create a JSON number from + @param[in] val an integer to create a JSON number from @note This constructor allows to pass enums directly to a constructor. As C++ has no way of specifying the type of an anonymous enum explicitly, we @@ -1091,11 +1185,16 @@ class basic_json @liveexample{The example below shows the construction of a JSON integer number value from an anonymous enum.,basic_json__const_int} - @sa basic_json(const number_integer_t) + @sa @ref basic_json(const number_integer_t) -- create a number value + (integer) + @sa @ref basic_json(const CompatibleNumberIntegerType) -- create a number + value (integer) from a compatible number type + + @since version 1.0.0 */ - basic_json(const int value) + basic_json(const int val) : m_type(value_t::number_integer), - m_value(static_cast(value)) + m_value(static_cast(val)) {} /*! @@ -1109,7 +1208,7 @@ class basic_json @tparam CompatibleNumberIntegerType an integer type which is compatible to @ref number_integer_t. - @param[in] value an integer to create a JSON number from + @param[in] val an integer to create a JSON number from @complexity Constant. @@ -1117,16 +1216,20 @@ class basic_json integer number values from compatible types.,basic_json__CompatibleIntegerNumberType} - @sa basic_json(const number_integer_t) + @sa @ref basic_json(const number_integer_t) -- create a number value + (integer) + @sa @ref basic_json(const int) -- create a number value (integer) + + @since version 1.0.0 */ template::value and std::numeric_limits::is_integer, CompatibleNumberIntegerType>::type = 0> - basic_json(const CompatibleNumberIntegerType value) noexcept + basic_json(const CompatibleNumberIntegerType val) noexcept : m_type(value_t::number_integer), - m_value(static_cast(value)) + m_value(static_cast(val)) {} /*! @@ -1134,25 +1237,30 @@ class basic_json Create a floating-point number JSON value with a given content. - @param[in] value a floating-point value to create a JSON number from + @param[in] val a floating-point value to create a JSON number from @note RFC 7159 , section 6 disallows NaN values: > Numeric values that cannot be represented in the grammar below (such > as Infinity and NaN) are not permitted. - In case the parameter @a value is not a number, a JSON null value is + In case the parameter @a val is not a number, a JSON null value is created instead. @complexity Constant. @liveexample{The following example creates several floating-point values.,basic_json__number_float_t} + + @sa @ref basic_json(const CompatibleNumberFloatType) -- create a number + value (floating-point) from a compatible number type + + @since version 1.0.0 */ - basic_json(const number_float_t value) - : m_type(value_t::number_float), m_value(value) + basic_json(const number_float_t val) + : m_type(value_t::number_float), m_value(val) { // replace infinity and NAN by null - if (not std::isfinite(value)) + if (not std::isfinite(val)) { m_type = value_t::null; m_value = json_value(); @@ -1169,13 +1277,13 @@ class basic_json @tparam CompatibleNumberFloatType a floating-point type which is compatible to @ref number_float_t. - @param[in] value a floating-point to create a JSON number from + @param[in] val a floating-point to create a JSON number from @note RFC 7159 , section 6 disallows NaN values: > Numeric values that cannot be represented in the grammar below (such > as Infinity and NaN) are not permitted. - In case the parameter @a value is not a number, a JSON null value is + In case the parameter @a val is not a number, a JSON null value is created instead. @complexity Constant. @@ -1184,15 +1292,18 @@ class basic_json floating-point number values from compatible types.,basic_json__CompatibleNumberFloatType} - @sa basic_json(const number_float_t) + @sa @ref basic_json(const number_float_t) -- create a number value + (floating-point) + + @since version 1.0.0 */ template::value and std::is_floating_point::value>::type > - basic_json(const CompatibleNumberFloatType value) noexcept - : basic_json(number_float_t(value)) + basic_json(const CompatibleNumberFloatType val) noexcept + : basic_json(number_float_t(val)) {} /*! @@ -1210,7 +1321,7 @@ class basic_json 3. In all other cases, an array is created. The rules aim to create the best fit between a C++ initializer list and - JSON values. The ratioinale is as follows: + JSON values. The rationale is as follows: 1. The empty initializer list is written as `{}` which is exactly an empty JSON object. @@ -1249,35 +1360,38 @@ class basic_json @throw std::domain_error if @a type_deduction is `false`, @a manual_type is `value_t::object`, but @a init contains an element which is not a pair - whose first element is a string + whose first element is a string; example: `"cannot create object from + initializer list"` @complexity Linear in the size of the initializer list @a init. @liveexample{The example below shows how JSON values are created from initializer lists,basic_json__list_init_t} - @sa basic_json array(std::initializer_list) - create a JSON - array value from an initializer list - @sa basic_json object(std::initializer_list) - create a JSON - object value from an initializer list + @sa @ref array(std::initializer_list) -- create a JSON array + value from an initializer list + @sa @ref object(std::initializer_list) -- create a JSON object + value from an initializer list + + @since version 1.0.0 */ basic_json(std::initializer_list init, bool type_deduction = true, value_t manual_type = value_t::array) { // the initializer list could describe an object - bool is_object = true; + bool is_an_object = true; - // check if each element is an array with two elements whose first element - // is a string + // check if each element is an array with two elements whose first + // element is a string for (const auto& element : init) { - if (element.m_type != value_t::array or element.size() != 2 - or element[0].m_type != value_t::string) + if (not element.is_array() or element.size() != 2 + or not element[0].is_string()) { // we found an element that makes it impossible to use the // initializer list as object - is_object = false; + is_an_object = false; break; } } @@ -1288,22 +1402,24 @@ class basic_json // if array is wanted, do not create an object though possible if (manual_type == value_t::array) { - is_object = false; + is_an_object = false; } // if object is wanted but impossible, throw an exception - if (manual_type == value_t::object and not is_object) + if (manual_type == value_t::object and not is_an_object) { throw std::domain_error("cannot create object from initializer list"); } } - if (is_object) + if (is_an_object) { // the initializer list is a list of pairs -> create object m_type = value_t::object; m_value = value_t::object; + assert(m_value.object != nullptr); + for (auto& element : init) { m_value.object->emplace(std::move(*(element[0].m_value.string)), std::move(element[1])); @@ -1329,9 +1445,9 @@ class basic_json basic_json(std::initializer_list, bool, value_t)). These cases are: 1. creating an array whose elements are all pairs whose first element is a - string - in this case, the initializer list constructor would create an + string -- in this case, the initializer list constructor would create an object, taking the first elements as keys - 2. creating an empty array - passing the empty initializer list to the + 2. creating an empty array -- passing the empty initializer list to the initializer list constructor yields an empty object @param[in] init initializer list with JSON values to create an array from @@ -1344,10 +1460,12 @@ class basic_json @liveexample{The following code shows an example for the @ref array function.,array} - @sa basic_json(std::initializer_list, bool, value_t) - create a - JSON value from an initializer list - @sa basic_json object(std::initializer_list) - create a JSON - object value from an initializer list + @sa @ref basic_json(std::initializer_list, bool, value_t) -- + create a JSON value from an initializer list + @sa @ref object(std::initializer_list) -- create a JSON object + value from an initializer list + + @since version 1.0.0 */ static basic_json array(std::initializer_list init = std::initializer_list()) @@ -1359,33 +1477,35 @@ class basic_json @brief explicitly create an object from an initializer list Creates a JSON object value from a given initializer list. The initializer - lists elements must be pairs, and their first elments must be strings. If + lists elements must be pairs, and their first elements must be strings. If the initializer list is empty, the empty object `{}` is created. @note This function is only added for symmetry reasons. In contrast to the - related function @ref basic_json array(std::initializer_list), - there are no cases which can only be expressed by this function. That is, - any initializer list @a init can also be passed to the initializer list - constructor @ref basic_json(std::initializer_list, bool, - value_t). + related function @ref array(std::initializer_list), there are + no cases which can only be expressed by this function. That is, any + initializer list @a init can also be passed to the initializer list + constructor + @ref basic_json(std::initializer_list, bool, value_t). @param[in] init initializer list to create an object from (optional) @return JSON object value @throw std::domain_error if @a init is not a pair whose first elements are - strings; thrown by @ref basic_json(std::initializer_list, bool, - value_t) + strings; thrown by + @ref basic_json(std::initializer_list, bool, value_t) @complexity Linear in the size of @a init. @liveexample{The following code shows an example for the @ref object function.,object} - @sa basic_json(std::initializer_list, bool, value_t) - create a - JSON value from an initializer list - @sa basic_json array(std::initializer_list) - create a JSON - array value from an initializer list + @sa @ref basic_json(std::initializer_list, bool, value_t) -- + create a JSON value from an initializer list + @sa @ref array(std::initializer_list) -- create a JSON array + value from an initializer list + + @since version 1.0.0 */ static basic_json object(std::initializer_list init = std::initializer_list()) @@ -1396,23 +1516,25 @@ class basic_json /*! @brief construct an array with count copies of given value - Constructs a JSON array value by creating @a count copies of a passed - value. In case @a count is `0`, an empty array is created. As postcondition, - `std::distance(begin(),end()) == count` holds. + Constructs a JSON array value by creating @a cnt copies of a passed + value. In case @a cnt is `0`, an empty array is created. As postcondition, + `std::distance(begin(),end()) == cnt` holds. - @param[in] count the number of JSON copies of @a value to create - @param[in] value the JSON value to copy + @param[in] cnt the number of JSON copies of @a val to create + @param[in] val the JSON value to copy - @complexity Linear in @a count. + @complexity Linear in @a cnt. @liveexample{The following code shows examples for the @ref basic_json(size_type\, const basic_json&) constructor.,basic_json__size_type_basic_json} + + @since version 1.0.0 */ - basic_json(size_type count, const basic_json& value) + basic_json(size_type cnt, const basic_json& val) : m_type(value_t::array) { - m_value.array = create(count, value); + m_value.array = create(cnt, val); } /*! @@ -1434,16 +1556,20 @@ class basic_json @param[in] last end of the range to copy from (excluded) @throw std::domain_error if iterators are not compatible; that is, do not - belong to the same JSON value + belong to the same JSON value; example: `"iterators are not compatible"` @throw std::out_of_range if iterators are for a primitive type (number, - boolean, or string) where an out of range error can be detected easily + boolean, or string) where an out of range error can be detected easily; + example: `"iterators out of range"` @throw std::bad_alloc if allocation for object, array, or string fails - @throw std::domain_error if called with a null value + @throw std::domain_error if called with a null value; example: `"cannot use + construct with iterators from null"` @complexity Linear in distance between @a first and @a last. @liveexample{The example below shows several ways to create JSON values by specifying a subrange with iterators.,basic_json__InputIt_InputIt} + + @since version 1.0.0 */ template m_value.number_integer; break; } case value_t::number_float: { + assert(first.m_object != nullptr); m_value.number_float = first.m_object->m_value.number_float; break; } case value_t::boolean: { + assert(first.m_object != nullptr); m_value.boolean = first.m_object->m_value.boolean; break; } case value_t::string: { + assert(first.m_object != nullptr); m_value = *first.m_object->m_value.string; break; } @@ -1520,6 +1650,7 @@ class basic_json default: { + assert(first.m_object != nullptr); throw std::domain_error("cannot use construct with iterators from " + first.m_object->type_name()); } } @@ -1546,53 +1677,57 @@ class basic_json @liveexample{The following code shows an example for the copy constructor.,basic_json__basic_json} + + @since version 1.0.0 */ basic_json(const basic_json& other) : m_type(other.m_type) { switch (m_type) { - case (value_t::null): - case (value_t::discarded): - { - break; - } - - case (value_t::object): + case value_t::object: { + assert(other.m_value.object != nullptr); m_value = *other.m_value.object; break; } - case (value_t::array): + case value_t::array: { + assert(other.m_value.array != nullptr); m_value = *other.m_value.array; break; } - case (value_t::string): + case value_t::string: { + assert(other.m_value.string != nullptr); m_value = *other.m_value.string; break; } - case (value_t::boolean): + case value_t::boolean: { m_value = other.m_value.boolean; break; } - case (value_t::number_integer): + case value_t::number_integer: { m_value = other.m_value.number_integer; break; } - case (value_t::number_float): + case value_t::number_float: { m_value = other.m_value.number_float; break; } + + default: + { + break; + } } } @@ -1611,6 +1746,8 @@ class basic_json @liveexample{The code below shows the move constructor explicitly called via std::move.,basic_json__moveconstructor} + + @since version 1.0.0 */ basic_json(basic_json&& other) noexcept : m_type(std::move(other.m_type)), @@ -1639,6 +1776,8 @@ class basic_json creates a copy of value `a` which is then swapped with `b`. Finally\, the copy of `a` (which is the null value after the swap) is destroyed.,basic_json__copyassignment} + + @since version 1.0.0 */ reference& operator=(basic_json other) noexcept ( std::is_nothrow_move_constructible::value and @@ -1663,12 +1802,14 @@ class basic_json @requirement This function satisfies the Container requirements: - The complexity is linear. - All stored elements are destroyed and all memory is freed. + + @since version 1.0.0 */ ~basic_json() { switch (m_type) { - case (value_t::object): + case value_t::object: { AllocatorType alloc; alloc.destroy(m_value.object); @@ -1676,7 +1817,7 @@ class basic_json break; } - case (value_t::array): + case value_t::array: { AllocatorType alloc; alloc.destroy(m_value.array); @@ -1684,7 +1825,7 @@ class basic_json break; } - case (value_t::string): + case value_t::string: { AllocatorType alloc; alloc.destroy(m_value.string); @@ -1700,6 +1841,7 @@ class basic_json } } + /// @} public: /////////////////////// @@ -1712,7 +1854,7 @@ class basic_json /*! @brief serialization - Serialization function for JSON values. The function tries to mimick + Serialization function for JSON values. The function tries to mimic Python's @p json.dumps() function, and currently supports its @p indent parameter. @@ -1726,9 +1868,11 @@ class basic_json @complexity Linear. @liveexample{The following example shows the effect of different @a indent - parameters to the result of the serializaion.,dump} + parameters to the result of the serialization.,dump} @see https://docs.python.org/2/library/json.html#json.dump + + @since version 1.0.0 */ string_t dump(const int indent = -1) const { @@ -1758,6 +1902,8 @@ class basic_json @liveexample{The following code exemplifies @ref type() for all JSON types.,type} + + @since version 1.0.0 */ value_t type() const noexcept { @@ -1777,6 +1923,8 @@ class basic_json @liveexample{The following code exemplifies @ref is_primitive for all JSON types.,is_primitive} + + @since version 1.0.0 */ bool is_primitive() const noexcept { @@ -1795,6 +1943,8 @@ class basic_json @liveexample{The following code exemplifies @ref is_structured for all JSON types.,is_structured} + + @since version 1.0.0 */ bool is_structured() const noexcept { @@ -1812,6 +1962,8 @@ class basic_json @liveexample{The following code exemplifies @ref is_null for all JSON types.,is_null} + + @since version 1.0.0 */ bool is_null() const noexcept { @@ -1829,6 +1981,8 @@ class basic_json @liveexample{The following code exemplifies @ref is_boolean for all JSON types.,is_boolean} + + @since version 1.0.0 */ bool is_boolean() const noexcept { @@ -1841,12 +1995,18 @@ class basic_json This function returns true iff the JSON value is a number. This includes both integer and floating-point values. - @return `true` if type is number, `false` otherwise. + @return `true` if type is number (regardless whether integer or + floating-type), `false` otherwise. @complexity Constant. @liveexample{The following code exemplifies @ref is_number for all JSON types.,is_number} + + @sa @ref is_number_integer() -- check if value is an integer number + @sa @ref is_number_float() -- check if value is a floating-point number + + @since version 1.0.0 */ bool is_number() const noexcept { @@ -1865,6 +2025,11 @@ class basic_json @liveexample{The following code exemplifies @ref is_number_integer for all JSON types.,is_number_integer} + + @sa @ref is_number() -- check if value is a number + @sa @ref is_number_float() -- check if value is a floating-point number + + @since version 1.0.0 */ bool is_number_integer() const noexcept { @@ -1883,6 +2048,11 @@ class basic_json @liveexample{The following code exemplifies @ref is_number_float for all JSON types.,is_number_float} + + @sa @ref is_number() -- check if value is number + @sa @ref is_number_integer() -- check if value is an integer number + + @since version 1.0.0 */ bool is_number_float() const noexcept { @@ -1900,6 +2070,8 @@ class basic_json @liveexample{The following code exemplifies @ref is_object for all JSON types.,is_object} + + @since version 1.0.0 */ bool is_object() const noexcept { @@ -1917,6 +2089,8 @@ class basic_json @liveexample{The following code exemplifies @ref is_array for all JSON types.,is_array} + + @since version 1.0.0 */ bool is_array() const noexcept { @@ -1934,6 +2108,8 @@ class basic_json @liveexample{The following code exemplifies @ref is_string for all JSON types.,is_string} + + @since version 1.0.0 */ bool is_string() const noexcept { @@ -1956,6 +2132,8 @@ class basic_json @liveexample{The following code exemplifies @ref is_discarded for all JSON types.,is_discarded} + + @since version 1.0.0 */ bool is_discarded() const noexcept { @@ -1974,6 +2152,8 @@ class basic_json @liveexample{The following code exemplifies the value_t operator for all JSON types.,operator__value_t} + + @since version 1.0.0 */ operator value_t() const noexcept { @@ -1995,32 +2175,28 @@ class basic_json , int>::type = 0> T get_impl(T*) const { - switch (m_type) + if (is_object()) { - case (value_t::object): - { - return T(m_value.object->begin(), m_value.object->end()); - } - default: - { - throw std::domain_error("type must be object, but is " + type_name()); - } + assert(m_value.object != nullptr); + return T(m_value.object->begin(), m_value.object->end()); + } + else + { + throw std::domain_error("type must be object, but is " + type_name()); } } /// get an object (explicit) object_t get_impl(object_t*) const { - switch (m_type) + if (is_object()) { - case (value_t::object): - { - return *(m_value.object); - } - default: - { - throw std::domain_error("type must be object, but is " + type_name()); - } + assert(m_value.object != nullptr); + return *(m_value.object); + } + else + { + throw std::domain_error("type must be object, but is " + type_name()); } } @@ -2035,22 +2211,20 @@ class basic_json , int>::type = 0> T get_impl(T*) const { - switch (m_type) + if (is_array()) { - case (value_t::array): + T to_vector; + assert(m_value.array != nullptr); + std::transform(m_value.array->begin(), m_value.array->end(), + std::inserter(to_vector, to_vector.end()), [](basic_json i) { - T to_vector; - std::transform(m_value.array->begin(), m_value.array->end(), - std::inserter(to_vector, to_vector.end()), [](basic_json i) - { - return i.get(); - }); - return to_vector; - } - default: - { - throw std::domain_error("type must be array, but is " + type_name()); - } + return i.get(); + }); + return to_vector; + } + else + { + throw std::domain_error("type must be array, but is " + type_name()); } } @@ -2062,23 +2236,21 @@ class basic_json , int>::type = 0> std::vector get_impl(std::vector*) const { - switch (m_type) + if (is_array()) { - case (value_t::array): + std::vector to_vector; + assert(m_value.array != nullptr); + to_vector.reserve(m_value.array->size()); + std::transform(m_value.array->begin(), m_value.array->end(), + std::inserter(to_vector, to_vector.end()), [](basic_json i) { - std::vector to_vector; - to_vector.reserve(m_value.array->size()); - std::transform(m_value.array->begin(), m_value.array->end(), - std::inserter(to_vector, to_vector.end()), [](basic_json i) - { - return i.get(); - }); - return to_vector; - } - default: - { - throw std::domain_error("type must be array, but is " + type_name()); - } + return i.get(); + }); + return to_vector; + } + else + { + throw std::domain_error("type must be array, but is " + type_name()); } } @@ -2090,32 +2262,28 @@ class basic_json , int>::type = 0> T get_impl(T*) const { - switch (m_type) + if (is_array()) { - case (value_t::array): - { - return T(m_value.array->begin(), m_value.array->end()); - } - default: - { - throw std::domain_error("type must be array, but is " + type_name()); - } + assert(m_value.array != nullptr); + return T(m_value.array->begin(), m_value.array->end()); + } + else + { + throw std::domain_error("type must be array, but is " + type_name()); } } /// get an array (explicit) array_t get_impl(array_t*) const { - switch (m_type) + if (is_array()) { - case (value_t::array): - { - return *(m_value.array); - } - default: - { - throw std::domain_error("type must be array, but is " + type_name()); - } + assert(m_value.array != nullptr); + return *(m_value.array); + } + else + { + throw std::domain_error("type must be array, but is " + type_name()); } } @@ -2126,16 +2294,14 @@ class basic_json , int>::type = 0> T get_impl(T*) const { - switch (m_type) + if (is_string()) { - case (value_t::string): - { - return *m_value.string; - } - default: - { - throw std::domain_error("type must be string, but is " + type_name()); - } + assert(m_value.string != nullptr); + return *m_value.string; + } + else + { + throw std::domain_error("type must be string, but is " + type_name()); } } @@ -2148,14 +2314,16 @@ class basic_json { switch (m_type) { - case (value_t::number_integer): + case value_t::number_integer: { return static_cast(m_value.number_integer); } - case (value_t::number_float): + + case value_t::number_float: { return static_cast(m_value.number_float); } + default: { throw std::domain_error("type must be number, but is " + type_name()); @@ -2166,16 +2334,13 @@ class basic_json /// get a boolean (explicit) boolean_t get_impl(boolean_t*) const { - switch (m_type) + if (is_boolean()) { - case (value_t::boolean): - { - return m_value.boolean; - } - default: - { - throw std::domain_error("type must be boolean, but is " + type_name()); - } + return m_value.boolean; + } + else + { + throw std::domain_error("type must be boolean, but is " + type_name()); } } @@ -2281,15 +2446,15 @@ class basic_json @return copy of the JSON value, converted to type @a ValueType @throw std::domain_error in case passed type @a ValueType is incompatible - to JSON + to JSON; example: `"type must be object, but is null"` @complexity Linear in the size of the JSON value. - @liveexample{The example below shows serveral conversions from JSON values + @liveexample{The example below shows several conversions from JSON values to other types. There a few things to note: (1) Floating-point numbers can be converted to integers\, (2) A JSON array can be converted to a standard `std::vector`\, (3) A JSON object can be converted to C++ - assiciative containers such as `std::unordered_map`.,get__ValueType_const} @internal @@ -2299,6 +2464,8 @@ class basic_json @sa @ref operator ValueType() const for implicit conversion @sa @ref get() for pointer-member access + + @since version 1.0.0 */ template::value - and std::is_const< typename std::remove_pointer::type >::value + and std::is_const::type>::value , int>::type = 0> const PointerType get_ptr() const noexcept { @@ -2455,12 +2625,14 @@ class basic_json /*! @brief get a value (implicit) - Implict type conversion between the JSON value and a compatible value. The + Implicit type conversion between the JSON value and a compatible value. The call is realized by calling @ref get() const. @tparam ValueType non-pointer type compatible to the JSON value, for instance `int` for JSON integer numbers, `bool` for JSON booleans, or - `std::vector` types for JSON arrays + `std::vector` types for JSON arrays. The character type of @ref string_t + as well as an initializer list of this type is excluded to avoid + ambiguities as these types implicitly convert to `std::string`. @return copy of the JSON value, converted to type @a ValueType @@ -2469,16 +2641,20 @@ class basic_json @complexity Linear in the size of the JSON value. - @liveexample{The example below shows serveral conversions from JSON values + @liveexample{The example below shows several conversions from JSON values to other types. There a few things to note: (1) Floating-point numbers can be converted to integers\, (2) A JSON array can be converted to a standard `std::vector`\, (3) A JSON object can be converted to C++ - assiciative containers such as `std::unordered_map`.,operator__ValueType} + + @since version 1.0.0 */ template::value + and not std::is_same::value + and not std::is_same>::value , int>::type = 0> operator ValueType() const { @@ -2506,24 +2682,38 @@ class basic_json @return reference to the element at index @a idx - @throw std::domain_error if JSON is not an array + @throw std::domain_error if the JSON value is not an array; example: + `"cannot use at() with string"` @throw std::out_of_range if the index @a idx is out of range of the array; - that is, `idx >= size()` + that is, `idx >= size()`; example: `"array index 7 is out of range"` @complexity Constant. @liveexample{The example below shows how array elements can be read and written using at.,at__size_type} + + @since version 1.0.0 */ reference at(size_type idx) { // at only works for arrays - if (m_type != value_t::array) + if (is_array()) + { + try + { + assert(m_value.array != nullptr); + return m_value.array->at(idx); + } + catch (std::out_of_range&) + { + // create better exception explanation + throw std::out_of_range("array index " + std::to_string(idx) + " is out of range"); + } + } + else { throw std::domain_error("cannot use at() with " + type_name()); } - - return m_value.array->at(idx); } /*! @@ -2536,24 +2726,38 @@ class basic_json @return const reference to the element at index @a idx - @throw std::domain_error if JSON is not an array + @throw std::domain_error if the JSON value is not an array; example: + `"cannot use at() with string"` @throw std::out_of_range if the index @a idx is out of range of the array; - that is, `idx >= size()` + that is, `idx >= size()`; example: `"array index 7 is out of range"` @complexity Constant. @liveexample{The example below shows how array elements can be read using at.,at__size_type_const} + + @since version 1.0.0 */ const_reference at(size_type idx) const { // at only works for arrays - if (m_type != value_t::array) + if (is_array()) + { + try + { + assert(m_value.array != nullptr); + return m_value.array->at(idx); + } + catch (std::out_of_range&) + { + // create better exception explanation + throw std::out_of_range("array index " + std::to_string(idx) + " is out of range"); + } + } + else { throw std::domain_error("cannot use at() with " + type_name()); } - - return m_value.array->at(idx); } /*! @@ -2566,24 +2770,42 @@ class basic_json @return reference to the element at key @a key - @throw std::domain_error if JSON is not an object + @throw std::domain_error if the JSON value is not an object; example: + `"cannot use at() with boolean"` @throw std::out_of_range if the key @a key is is not stored in the object; - that is, `find(key) == end()` + that is, `find(key) == end()`; example: `"key "the fast" not found"` @complexity Logarithmic in the size of the container. @liveexample{The example below shows how object elements can be read and written using at.,at__object_t_key_type} + + @sa @ref operator[](const typename object_t::key_type&) for unchecked + access by reference + @sa @ref value() for access by value with a default value + + @since version 1.0.0 */ reference at(const typename object_t::key_type& key) { // at only works for objects - if (m_type != value_t::object) + if (is_object()) + { + try + { + assert(m_value.object != nullptr); + return m_value.object->at(key); + } + catch (std::out_of_range&) + { + // create better exception explanation + throw std::out_of_range("key '" + key + "' not found"); + } + } + else { throw std::domain_error("cannot use at() with " + type_name()); } - - return m_value.object->at(key); } /*! @@ -2596,24 +2818,42 @@ class basic_json @return const reference to the element at key @a key - @throw std::domain_error if JSON is not an object + @throw std::domain_error if the JSON value is not an object; example: + `"cannot use at() with boolean"` @throw std::out_of_range if the key @a key is is not stored in the object; - that is, `find(key) == end()` + that is, `find(key) == end()`; example: `"key "the fast" not found"` @complexity Logarithmic in the size of the container. @liveexample{The example below shows how object elements can be read using at.,at__object_t_key_type_const} + + @sa @ref operator[](const typename object_t::key_type&) for unchecked + access by reference + @sa @ref value() for access by value with a default value + + @since version 1.0.0 */ const_reference at(const typename object_t::key_type& key) const { // at only works for objects - if (m_type != value_t::object) + if (is_object()) + { + try + { + assert(m_value.object != nullptr); + return m_value.object->at(key); + } + catch (std::out_of_range&) + { + // create better exception explanation + throw std::out_of_range("key '" + key + "' not found"); + } + } + else { throw std::domain_error("cannot use at() with " + type_name()); } - - return m_value.object->at(key); } /*! @@ -2629,7 +2869,8 @@ class basic_json @return reference to the element at index @a idx - @throw std::domain_error if JSON is not an array or null + @throw std::domain_error if JSON is not an array or null; example: `"cannot + use operator[] with null"` @complexity Constant if @a idx is in the range of the array. Otherwise linear in `idx - size()`. @@ -2637,28 +2878,33 @@ class basic_json @liveexample{The example below shows how array elements can be read and written using [] operator. Note the addition of `null` values.,operatorarray__size_type} + + @since version 1.0.0 */ reference operator[](size_type idx) { // implicitly convert null to object - if (m_type == value_t::null) + if (is_null()) { m_type = value_t::array; m_value.array = create(); } // [] only works for arrays - if (m_type != value_t::array) + if (is_array()) + { + assert(m_value.array != nullptr); + for (size_t i = m_value.array->size(); i <= idx; ++i) + { + m_value.array->push_back(basic_json()); + } + + return m_value.array->operator[](idx); + } + else { throw std::domain_error("cannot use operator[] with " + type_name()); } - - for (size_t i = m_value.array->size(); i <= idx; ++i) - { - m_value.array->push_back(basic_json()); - } - - return m_value.array->operator[](idx); } /*! @@ -2670,22 +2916,28 @@ class basic_json @return const reference to the element at index @a idx - @throw std::domain_error if JSON is not an array + @throw std::domain_error if JSON is not an array; example: `"cannot use + operator[] with null"` @complexity Constant. @liveexample{The example below shows how array elements can be read using the [] operator.,operatorarray__size_type_const} + + @since version 1.0.0 */ const_reference operator[](size_type idx) const { // at only works for arrays - if (m_type != value_t::array) + if (is_array()) + { + assert(m_value.array != nullptr); + return m_value.array->operator[](idx); + } + else { throw std::domain_error("cannot use operator[] with " + type_name()); } - - return m_value.array->operator[](idx); } /*! @@ -2701,56 +2953,81 @@ class basic_json @return reference to the element at key @a key - @throw std::domain_error if JSON is not an object or null + @throw std::domain_error if JSON is not an object or null; example: + `"cannot use operator[] with null"` @complexity Logarithmic in the size of the container. @liveexample{The example below shows how object elements can be read and written using the [] operator.,operatorarray__key_type} + + @sa @ref at(const typename object_t::key_type&) for access by reference + with range checking + @sa @ref value() for access by value with a default value + + @since version 1.0.0 */ reference operator[](const typename object_t::key_type& key) { // implicitly convert null to object - if (m_type == value_t::null) + if (is_null()) { m_type = value_t::object; m_value.object = create(); } // [] only works for objects - if (m_type != value_t::object) + if (is_object()) + { + assert(m_value.object != nullptr); + return m_value.object->operator[](key); + } + else { throw std::domain_error("cannot use operator[] with " + type_name()); } - - return m_value.object->operator[](key); } /*! - @brief access specified object element + @brief read-only access specified object element - Returns a reference to the element at with specified key @a key. + Returns a const reference to the element at with specified key @a key. No + bounds checking is performed. + + @warning If the element with key @a key does not exist, the behavior is + undefined. @param[in] key key of the element to access - @return reference to the element at key @a key + @return const reference to the element at key @a key - @throw std::domain_error if JSON is not an object or null + @throw std::domain_error if JSON is not an object; example: `"cannot use + operator[] with null"` @complexity Logarithmic in the size of the container. @liveexample{The example below shows how object elements can be read using the [] operator.,operatorarray__key_type_const} + + @sa @ref at(const typename object_t::key_type&) for access by reference + with range checking + @sa @ref value() for access by value with a default value + + @since version 1.0.0 */ const_reference operator[](const typename object_t::key_type& key) const { - // at only works for objects - if (m_type != value_t::object) + // [] only works for objects + if (is_object()) + { + assert(m_value.object != nullptr); + assert(m_value.object->find(key) != m_value.object->end()); + return m_value.object->find(key)->second; + } + else { throw std::domain_error("cannot use operator[] with " + type_name()); } - - return m_value.object->operator[](key); } /*! @@ -2768,60 +3045,168 @@ class basic_json @return reference to the element at key @a key - @throw std::domain_error if JSON is not an object or null + @throw std::domain_error if JSON is not an object or null; example: + `"cannot use operator[] with null"` @complexity Logarithmic in the size of the container. @liveexample{The example below shows how object elements can be read and written using the [] operator.,operatorarray__key_type} + + @sa @ref at(const typename object_t::key_type&) for access by reference + with range checking + @sa @ref value() for access by value with a default value + + @since version 1.0.0 */ template reference operator[](const T (&key)[n]) { // implicitly convert null to object - if (m_type == value_t::null) + if (is_null()) { m_type = value_t::object; m_value = value_t::object; } // at only works for objects - if (m_type != value_t::object) + if (is_object()) + { + assert(m_value.object != nullptr); + return m_value.object->operator[](key); + } + else { throw std::domain_error("cannot use operator[] with " + type_name()); } - - return m_value.object->operator[](key); } /*! - @brief access specified object element + @brief read-only access specified object element - Returns a reference to the element at with specified key @a key. + Returns a const reference to the element at with specified key @a key. No + bounds checking is performed. + + @warning If the element with key @a key does not exist, the behavior is + undefined. @note This function is required for compatibility reasons with Clang. @param[in] key key of the element to access - @return reference to the element at key @a key + @return const reference to the element at key @a key - @throw std::domain_error if JSON is not an object or null + @throw std::domain_error if JSON is not an object; example: `"cannot use + operator[] with null"` @complexity Logarithmic in the size of the container. @liveexample{The example below shows how object elements can be read using the [] operator.,operatorarray__key_type_const} + + @sa @ref at(const typename object_t::key_type&) for access by reference + with range checking + @sa @ref value() for access by value with a default value + + @since version 1.0.0 */ template const_reference operator[](const T (&key)[n]) const { // at only works for objects - if (m_type != value_t::object) + if (is_object()) + { + assert(m_value.object != nullptr); + assert(m_value.object->find(key) != m_value.object->end()); + return m_value.object->find(key)->second; + } + else { throw std::domain_error("cannot use operator[] with " + type_name()); } + } - return m_value.object->operator[](key); + /*! + @brief access specified object element with default value + + Returns either a copy of an object's element at the specified key @a key or + a given default value if no element with key @a key exists. + + The function is basically equivalent to executing + @code {.cpp} + try { + return at(key); + } catch(std::out_of_range) { + return default_value; + } + @endcode + + @note Unlike @ref at(const typename object_t::key_type&), this function + does not throw if the given key @a key was not found. + + @note Unlike @ref operator[](const typename object_t::key_type& key), this + function does not implicitly add an element to the position defined by @a + key. This function is furthermore also applicable to const objects. + + @param[in] key key of the element to access + @param[in] default_value the value to return if @a key is not found + + @tparam ValueType type compatible to JSON values, for instance `int` for + JSON integer numbers, `bool` for JSON booleans, or `std::vector` types for + JSON arrays. Note the type of the expected value at @a key and the default + value @a default_value must be compatible. + + @return copy of the element at key @a key or @a default_value if @a key + is not found + + @throw std::domain_error if JSON is not an object; example: `"cannot use + value() with null"` + + @complexity Logarithmic in the size of the container. + + @liveexample{The example below shows how object elements can be queried + with a default value.,basic_json__value} + + @sa @ref at(const typename object_t::key_type&) for access by reference + with range checking + @sa @ref operator[](const typename object_t::key_type&) for unchecked + access by reference + + @since version 1.0.0 + */ + template ::value + , int>::type = 0> + ValueType value(const typename object_t::key_type& key, ValueType default_value) const + { + // at only works for objects + if (is_object()) + { + // if key is found, return value and given default value otherwise + const auto it = find(key); + if (it != end()) + { + return *it; + } + else + { + return default_value; + } + } + else + { + throw std::domain_error("cannot use value() with " + type_name()); + } + } + + /*! + @brief overload for a default value of type const char* + @copydoc basic_json::value() + */ + string_t value(const typename object_t::key_type& key, const char* default_value) const + { + return value(key, string_t(default_value)); } /*! @@ -2841,6 +3226,8 @@ class basic_json @throw std::out_of_range when called on null value @liveexample{The following code shows an example for @ref front.,front} + + @since version 1.0.0 */ reference front() { @@ -2873,6 +3260,8 @@ class basic_json @throw std::out_of_range when called on null value. @liveexample{The following code shows an example for @ref back.,back} + + @since version 1.0.0 */ reference back() { @@ -2897,7 +3286,7 @@ class basic_json Removes the element specified by iterator @a pos. Invalidates iterators and references at or after the point of the erase, including the end() iterator. The iterator @a pos must be valid and dereferenceable. Thus the - end() iterator (which is valid, but is not dereferencable) cannot be used + end() iterator (which is valid, but is not dereferenceable) cannot be used as a value for @a pos. If called on a primitive type other than null, the resulting JSON value @@ -2909,11 +3298,13 @@ class basic_json @tparam InteratorType an @ref iterator or @ref const_iterator - @throw std::domain_error if called on a `null` value + @throw std::domain_error if called on a `null` value; example: `"cannot use + erase() with null"` @throw std::domain_error if called on an iterator which does not belong to - the current JSON value - @throw std::out_of_range if called on a primitive type with invalid iterator - (i.e., any iterator which is not end()) + the current JSON value; example: `"iterator does not fit current value"` + @throw std::out_of_range if called on a primitive type with invalid + iterator (i.e., any iterator which is not end()); example: `"iterator out + of range"` @complexity The complexity depends on the type: - objects: amortized constant @@ -2923,6 +3314,15 @@ class basic_json @liveexample{The example shows the result of erase for different JSON types.,erase__IteratorType} + + @sa @ref erase(InteratorType, InteratorType) -- removes the elements in the + given range + @sa @ref erase(const typename object_t::key_type&) -- removes the element + from an object at the given key + @sa @ref erase(const size_type) -- removes the element from an array at the + given index + + @since version 1.0.0 */ template erase(pos.m_it.object_iterator); break; } case value_t::array: { + assert(m_value.array != nullptr); result.m_it.array_iterator = m_value.array->erase(pos.m_it.array_iterator); break; } @@ -3001,11 +3403,13 @@ class basic_json @tparam InteratorType an @ref iterator or @ref const_iterator - @throw std::domain_error if called on a `null` value + @throw std::domain_error if called on a `null` value; example: `"cannot use + erase() with null"` @throw std::domain_error if called on iterators which does not belong to - the current JSON value - @throw std::out_of_range if called on a primitive type with invalid iterators - (i.e., if `first != begin()` and `last != end()`) + the current JSON value; example: `"iterators do not fit current value"` + @throw std::out_of_range if called on a primitive type with invalid + iterators (i.e., if `first != begin()` and `last != end()`); example: + `"iterators out of range"` @complexity The complexity depends on the type: - objects: `log(size()) + std::distance(first, last)` @@ -3016,6 +3420,14 @@ class basic_json @liveexample{The example shows the result of erase for different JSON types.,erase__IteratorType_IteratorType} + + @sa @ref erase(InteratorType) -- removes the element at a given position + @sa @ref erase(const typename object_t::key_type&) -- removes the element + from an object at the given key + @sa @ref erase(const size_type) -- removes the element from an array at the + given index + + @since version 1.0.0 */ template erase(first.m_it.object_iterator, last.m_it.object_iterator); break; @@ -3064,6 +3477,7 @@ class basic_json case value_t::array: { + assert(m_value.array != nullptr); result.m_it.array_iterator = m_value.array->erase(first.m_it.array_iterator, last.m_it.array_iterator); break; @@ -3071,7 +3485,7 @@ class basic_json default: { - throw std::domain_error("cannot use erase with " + type_name()); + throw std::domain_error("cannot use erase() with " + type_name()); } } @@ -3089,21 +3503,33 @@ class basic_json type, the return value will always be `0` (@a key was not found) or `1` (@a key was found). - @throw std::domain_error when called on a type other than JSON object + @throw std::domain_error when called on a type other than JSON object; + example: `"cannot use erase() with null"` @complexity `log(size()) + count(key)` @liveexample{The example shows the effect of erase.,erase__key_type} + + @sa @ref erase(InteratorType) -- removes the element at a given position + @sa @ref erase(InteratorType, InteratorType) -- removes the elements in the + given range + @sa @ref erase(const size_type) -- removes the element from an array at the + given index + + @since version 1.0.0 */ size_type erase(const typename object_t::key_type& key) { // this erase only works for objects - if (m_type != value_t::object) + if (is_object()) + { + assert(m_value.object != nullptr); + return m_value.object->erase(key); + } + else { throw std::domain_error("cannot use erase() with " + type_name()); } - - return m_value.object->erase(key); } /*! @@ -3113,27 +3539,40 @@ class basic_json @param[in] idx index of the element to remove - @throw std::domain_error when called on a type other than JSON array - @throw std::out_of_range when `idx >= size()` + @throw std::domain_error when called on a type other than JSON array; + example: `"cannot use erase() with null"` + @throw std::out_of_range when `idx >= size()`; example: `"index out of + range"` @complexity Linear in distance between @a idx and the end of the container. @liveexample{The example shows the effect of erase.,erase__size_type} + + @sa @ref erase(InteratorType) -- removes the element at a given position + @sa @ref erase(InteratorType, InteratorType) -- removes the elements in the + given range + @sa @ref erase(const typename object_t::key_type&) -- removes the element + from an object at the given key + + @since version 1.0.0 */ void erase(const size_type idx) { // this erase only works for arrays - if (m_type != value_t::array) + if (is_array()) + { + if (idx >= size()) + { + throw std::out_of_range("index out of range"); + } + + assert(m_value.array != nullptr); + m_value.array->erase(m_value.array->begin() + static_cast(idx)); + } + else { throw std::domain_error("cannot use erase() with " + type_name()); } - - if (idx >= size()) - { - throw std::out_of_range("index out of range"); - } - - m_value.array->erase(m_value.array->begin() + static_cast(idx)); } /*! @@ -3150,13 +3589,16 @@ class basic_json @complexity Logarithmic in the size of the JSON object. @liveexample{The example shows how find is used.,find__key_type} + + @since version 1.0.0 */ iterator find(typename object_t::key_type key) { auto result = end(); - if (m_type == value_t::object) + if (is_object()) { + assert(m_value.object != nullptr); result.m_it.object_iterator = m_value.object->find(key); } @@ -3171,8 +3613,9 @@ class basic_json { auto result = cend(); - if (m_type == value_t::object) + if (is_object()) { + assert(m_value.object != nullptr); result.m_it.object_iterator = m_value.object->find(key); } @@ -3194,11 +3637,14 @@ class basic_json @complexity Logarithmic in the size of the JSON object. @liveexample{The example shows how count is used.,count} + + @since version 1.0.0 */ size_type count(typename object_t::key_type key) const { // return 0 for all nonobject types - return (m_type == value_t::object) ? m_value.object->count(key) : 0; + assert(not is_object() or m_value.object != nullptr); + return is_object() ? m_value.object->count(key) : 0; } /// @} @@ -3226,6 +3672,8 @@ class basic_json - The complexity is constant. @liveexample{The following code shows an example for @ref begin.,begin} + + @since version 1.0.0 */ iterator begin() { @@ -3258,6 +3706,8 @@ class basic_json - Has the semantics of `const_cast(*this).begin()`. @liveexample{The following code shows an example for @ref cbegin.,cbegin} + + @since version 1.0.0 */ const_iterator cbegin() const { @@ -3281,6 +3731,8 @@ class basic_json - The complexity is constant. @liveexample{The following code shows an example for @ref end.,end} + + @since version 1.0.0 */ iterator end() { @@ -3313,6 +3765,8 @@ class basic_json - Has the semantics of `const_cast(*this).end()`. @liveexample{The following code shows an example for @ref cend.,cend} + + @since version 1.0.0 */ const_iterator cend() const { @@ -3335,6 +3789,8 @@ class basic_json - Has the semantics of `reverse_iterator(end())`. @liveexample{The following code shows an example for @ref rbegin.,rbegin} + + @since version 1.0.0 */ reverse_iterator rbegin() { @@ -3364,6 +3820,8 @@ class basic_json - Has the semantics of `reverse_iterator(begin())`. @liveexample{The following code shows an example for @ref rend.,rend} + + @since version 1.0.0 */ reverse_iterator rend() { @@ -3393,6 +3851,8 @@ class basic_json - Has the semantics of `const_cast(*this).rbegin()`. @liveexample{The following code shows an example for @ref crbegin.,crbegin} + + @since version 1.0.0 */ const_reverse_iterator crbegin() const { @@ -3414,12 +3874,43 @@ class basic_json - Has the semantics of `const_cast(*this).rend()`. @liveexample{The following code shows an example for @ref crend.,crend} + + @since version 1.0.0 */ const_reverse_iterator crend() const { return const_reverse_iterator(cbegin()); } + private: + // forward declaration + template class iteration_proxy; + + public: + /*! + @brief wrapper to access iterator member functions in range-based for + + This function allows to access @ref iterator::key() and @ref + iterator::value() during range-based for loops. In these loops, a reference + to the JSON values is returned, so there is no access to the underlying + iterator. + + @note The name of this function is not yet final and may change in the + future. + */ + static iteration_proxy iterator_wrapper(reference cont) + { + return iteration_proxy(cont); + } + + /*! + @copydoc iterator_wrapper(reference) + */ + static iteration_proxy iterator_wrapper(const_reference cont) + { + return iteration_proxy(cont); + } + /// @} @@ -3447,8 +3938,8 @@ class basic_json array | result of function array_t::empty() @complexity Constant, as long as @ref array_t and @ref object_t satisfy the - Container concept; that is, their empty() functions have - constant complexity. + Container concept; that is, their empty() functions have constant + complexity. @requirement This function satisfies the Container requirements: - The complexity is constant. @@ -3456,23 +3947,28 @@ class basic_json @liveexample{The following code uses @ref empty to check if a @ref json object contains any elements.,empty} + + @since version 1.0.0 */ bool empty() const noexcept { switch (m_type) { - case (value_t::null): + case value_t::null: { + // null values are empty return true; } - case (value_t::array): + case value_t::array: { + assert(m_value.array != nullptr); return m_value.array->empty(); } - case (value_t::object): + case value_t::object: { + assert(m_value.object != nullptr); return m_value.object->empty(); } @@ -3501,8 +3997,7 @@ class basic_json array | result of function array_t::size() @complexity Constant, as long as @ref array_t and @ref object_t satisfy the - Container concept; that is, their size() functions have - constant complexity. + Container concept; that is, their size() functions have constant complexity. @requirement This function satisfies the Container requirements: - The complexity is constant. @@ -3510,23 +4005,28 @@ class basic_json @liveexample{The following code calls @ref size on the different value types.,size} + + @since version 1.0.0 */ size_type size() const noexcept { switch (m_type) { - case (value_t::null): + case value_t::null: { + // null values are empty return 0; } - case (value_t::array): + case value_t::array: { + assert(m_value.array != nullptr); return m_value.array->size(); } - case (value_t::object): + case value_t::object: { + assert(m_value.object != nullptr); return m_value.object->size(); } @@ -3557,8 +4057,8 @@ class basic_json array | result of function array_t::max_size() @complexity Constant, as long as @ref array_t and @ref object_t satisfy the - Container concept; that is, their max_size() functions have - constant complexity. + Container concept; that is, their max_size() functions have constant + complexity. @requirement This function satisfies the Container requirements: - The complexity is constant. @@ -3567,18 +4067,22 @@ class basic_json @liveexample{The following code calls @ref max_size on the different value types. Note the output is implementation specific.,max_size} + + @since version 1.0.0 */ size_type max_size() const noexcept { switch (m_type) { - case (value_t::array): + case value_t::array: { + assert(m_value.array != nullptr); return m_value.array->max_size(); } - case (value_t::object): + case value_t::object: { + assert(m_value.object != nullptr); return m_value.object->max_size(); } @@ -3622,100 +4126,108 @@ class basic_json @liveexample{The example below shows the effect of @ref clear to different JSON types.,clear} + + @since version 1.0.0 */ void clear() noexcept { switch (m_type) { - case (value_t::null): - case (value_t::discarded): - { - break; - } - - case (value_t::number_integer): + case value_t::number_integer: { m_value.number_integer = 0; break; } - case (value_t::number_float): + case value_t::number_float: { m_value.number_float = 0.0; break; } - case (value_t::boolean): + case value_t::boolean: { m_value.boolean = false; break; } - case (value_t::string): + case value_t::string: { + assert(m_value.string != nullptr); m_value.string->clear(); break; } - case (value_t::array): + case value_t::array: { + assert(m_value.array != nullptr); m_value.array->clear(); break; } - case (value_t::object): + case value_t::object: { + assert(m_value.object != nullptr); m_value.object->clear(); break; } + + default: + { + break; + } } } /*! @brief add an object to an array - Appends the given element @a value to the end of the JSON value. If the + Appends the given element @a val to the end of the JSON value. If the function is called on a JSON null value, an empty array is created before - appending @a value. + appending @a val. - @param value the value to add to the JSON array + @param val the value to add to the JSON array - @throw std::domain_error when called on a type other than JSON array or null + @throw std::domain_error when called on a type other than JSON array or + null; example: `"cannot use push_back() with number"` @complexity Amortized constant. @liveexample{The example shows how `push_back` and `+=` can be used to add elements to a JSON array. Note how the `null` value was silently converted to a JSON array.,push_back} + + @since version 1.0.0 */ - void push_back(basic_json&& value) + void push_back(basic_json&& val) { // push_back only works for null objects or arrays - if (not(m_type == value_t::null or m_type == value_t::array)) + if (not(is_null() or is_array())) { throw std::domain_error("cannot use push_back() with " + type_name()); } // transform null object into an array - if (m_type == value_t::null) + if (is_null()) { m_type = value_t::array; m_value = value_t::array; } // add element to array (move semantics) - m_value.array->push_back(std::move(value)); + assert(m_value.array != nullptr); + m_value.array->push_back(std::move(val)); // invalidate object - value.m_type = value_t::null; + val.m_type = value_t::null; } /*! @brief add an object to an array @copydoc push_back(basic_json&&) */ - reference operator+=(basic_json&& value) + reference operator+=(basic_json&& val) { - push_back(std::move(value)); + push_back(std::move(val)); return *this; } @@ -3723,167 +4235,185 @@ class basic_json @brief add an object to an array @copydoc push_back(basic_json&&) */ - void push_back(const basic_json& value) + void push_back(const basic_json& val) { // push_back only works for null objects or arrays - if (not(m_type == value_t::null or m_type == value_t::array)) + if (not(is_null() or is_array())) { throw std::domain_error("cannot use push_back() with " + type_name()); } // transform null object into an array - if (m_type == value_t::null) + if (is_null()) { m_type = value_t::array; m_value = value_t::array; } // add element to array - m_value.array->push_back(value); + assert(m_value.array != nullptr); + m_value.array->push_back(val); } /*! @brief add an object to an array @copydoc push_back(basic_json&&) */ - reference operator+=(const basic_json& value) + reference operator+=(const basic_json& val) { - push_back(value); + push_back(val); return *this; } /*! @brief add an object to an object - Inserts the given element @a value to the JSON object. If the function is + Inserts the given element @a val to the JSON object. If the function is called on a JSON null value, an empty object is created before inserting @a - value. + val. - @param[in] value the value to add to the JSON object + @param[in] val the value to add to the JSON object @throw std::domain_error when called on a type other than JSON object or - null + null; example: `"cannot use push_back() with number"` @complexity Logarithmic in the size of the container, O(log(`size()`)). @liveexample{The example shows how `push_back` and `+=` can be used to add elements to a JSON object. Note how the `null` value was silently converted to a JSON object.,push_back__object_t__value} + + @since version 1.0.0 */ - void push_back(const typename object_t::value_type& value) + void push_back(const typename object_t::value_type& val) { // push_back only works for null objects or objects - if (not(m_type == value_t::null or m_type == value_t::object)) + if (not(is_null() or is_object())) { throw std::domain_error("cannot use push_back() with " + type_name()); } // transform null object into an object - if (m_type == value_t::null) + if (is_null()) { m_type = value_t::object; m_value = value_t::object; } // add element to array - m_value.object->insert(value); + assert(m_value.object != nullptr); + m_value.object->insert(val); } /*! @brief add an object to an object @copydoc push_back(const typename object_t::value_type&) */ - reference operator+=(const typename object_t::value_type& value) + reference operator+=(const typename object_t::value_type& val) { - push_back(value); - return operator[](value.first); + push_back(val); + return operator[](val.first); } /*! @brief inserts element - Inserts element @a value before iterator @a pos. + Inserts element @a val before iterator @a pos. @param[in] pos iterator before which the content will be inserted; may be the end() iterator - @param[in] value element to insert - @return iterator pointing to the inserted @a value. + @param[in] val element to insert + @return iterator pointing to the inserted @a val. - @throw std::domain_error if called on JSON values other than arrays - @throw std::domain_error if @a pos is not an iterator of *this + @throw std::domain_error if called on JSON values other than arrays; + example: `"cannot use insert() with string"` + @throw std::domain_error if @a pos is not an iterator of *this; example: + `"iterator does not fit current value"` @complexity Constant plus linear in the distance between pos and end of the container. @liveexample{The example shows how insert is used.,insert} + + @since version 1.0.0 */ - iterator insert(const_iterator pos, const basic_json& value) + iterator insert(const_iterator pos, const basic_json& val) { // insert only works for arrays - if (m_type != value_t::array) + if (is_array()) + { + // check if iterator pos fits to this JSON value + if (pos.m_object != this) + { + throw std::domain_error("iterator does not fit current value"); + } + + // insert to array and return iterator + iterator result(this); + assert(m_value.array != nullptr); + result.m_it.array_iterator = m_value.array->insert(pos.m_it.array_iterator, val); + return result; + } + else { throw std::domain_error("cannot use insert() with " + type_name()); } - - // check if iterator pos fits to this JSON value - if (pos.m_object != this) - { - throw std::domain_error("iterator does not fit current value"); - } - - // insert to array and return iterator - iterator result(this); - result.m_it.array_iterator = m_value.array->insert(pos.m_it.array_iterator, value); - return result; } /*! @brief inserts element @copydoc insert(const_iterator, const basic_json&) */ - iterator insert(const_iterator pos, basic_json&& value) + iterator insert(const_iterator pos, basic_json&& val) { - return insert(pos, value); + return insert(pos, val); } /*! @brief inserts elements - Inserts @a count copies of @a value before iterator @a pos. + Inserts @a cnt copies of @a val before iterator @a pos. @param[in] pos iterator before which the content will be inserted; may be the end() iterator - @param[in] count number of copies of @a value to insert - @param[in] value element to insert + @param[in] cnt number of copies of @a val to insert + @param[in] val element to insert @return iterator pointing to the first element inserted, or @a pos if - `count==0` + `cnt==0` - @throw std::domain_error if called on JSON values other than arrays - @throw std::domain_error if @a pos is not an iterator of *this + @throw std::domain_error if called on JSON values other than arrays; + example: `"cannot use insert() with string"` + @throw std::domain_error if @a pos is not an iterator of *this; example: + `"iterator does not fit current value"` - @complexity Linear in @a count plus linear in the distance between @a pos + @complexity Linear in @a cnt plus linear in the distance between @a pos and end of the container. @liveexample{The example shows how insert is used.,insert__count} + + @since version 1.0.0 */ - iterator insert(const_iterator pos, size_type count, const basic_json& value) + iterator insert(const_iterator pos, size_type cnt, const basic_json& val) { // insert only works for arrays - if (m_type != value_t::array) + if (is_array()) + { + // check if iterator pos fits to this JSON value + if (pos.m_object != this) + { + throw std::domain_error("iterator does not fit current value"); + } + + // insert to array and return iterator + iterator result(this); + assert(m_value.array != nullptr); + result.m_it.array_iterator = m_value.array->insert(pos.m_it.array_iterator, cnt, val); + return result; + } + else { throw std::domain_error("cannot use insert() with " + type_name()); } - - // check if iterator pos fits to this JSON value - if (pos.m_object != this) - { - throw std::domain_error("iterator does not fit current value"); - } - - // insert to array and return iterator - iterator result(this); - result.m_it.array_iterator = m_value.array->insert(pos.m_it.array_iterator, count, value); - return result; } /*! @@ -3896,12 +4426,16 @@ class basic_json @param[in] first begin of the range of elements to insert @param[in] last end of the range of elements to insert - @throw std::domain_error if called on JSON values other than arrays - @throw std::domain_error if @a pos is not an iterator of *this + @throw std::domain_error if called on JSON values other than arrays; + example: `"cannot use insert() with string"` + @throw std::domain_error if @a pos is not an iterator of *this; example: + `"iterator does not fit current value"` @throw std::domain_error if @a first and @a last do not belong to the same - JSON value + JSON value; example: `"iterators do not fit"` @throw std::domain_error if @a first or @a last are iterators into - container for which insert is called + container for which insert is called; example: `"passed iterators may not + belong to container"` + @return iterator pointing to the first element inserted, or @a pos if `first==last` @@ -3909,11 +4443,13 @@ class basic_json distance between @a pos and end of the container. @liveexample{The example shows how insert is used.,insert__range} + + @since version 1.0.0 */ iterator insert(const_iterator pos, const_iterator first, const_iterator last) { // insert only works for arrays - if (m_type != value_t::array) + if (not is_array()) { throw std::domain_error("cannot use insert() with " + type_name()); } @@ -3926,7 +4462,7 @@ class basic_json if (first.m_object != last.m_object) { - throw std::domain_error("iterators does not fit"); + throw std::domain_error("iterators do not fit"); } if (first.m_object == this or last.m_object == this) @@ -3936,8 +4472,11 @@ class basic_json // insert to array and return iterator iterator result(this); - result.m_it.array_iterator = m_value.array->insert(pos.m_it.array_iterator, - first.m_it.array_iterator, last.m_it.array_iterator); + assert(m_value.array != nullptr); + result.m_it.array_iterator = m_value.array->insert( + pos.m_it.array_iterator, + first.m_it.array_iterator, + last.m_it.array_iterator); return result; } @@ -3950,8 +4489,11 @@ class basic_json the end() iterator @param[in] ilist initializer list to insert the values from - @throw std::domain_error if called on JSON values other than arrays - @throw std::domain_error if @a pos is not an iterator of *this + @throw std::domain_error if called on JSON values other than arrays; + example: `"cannot use insert() with string"` + @throw std::domain_error if @a pos is not an iterator of *this; example: + `"iterator does not fit current value"` + @return iterator pointing to the first element inserted, or @a pos if `ilist` is empty @@ -3959,11 +4501,13 @@ class basic_json pos and end of the container. @liveexample{The example shows how insert is used.,insert__ilist} + + @since version 1.0.0 */ iterator insert(const_iterator pos, std::initializer_list ilist) { // insert only works for arrays - if (m_type != value_t::array) + if (not is_array()) { throw std::domain_error("cannot use insert() with " + type_name()); } @@ -3976,6 +4520,7 @@ class basic_json // insert to array and return iterator iterator result(this); + assert(m_value.array != nullptr); result.m_it.array_iterator = m_value.array->insert(pos.m_it.array_iterator, ilist); return result; } @@ -3994,6 +4539,8 @@ class basic_json @liveexample{The example below shows how JSON arrays can be swapped.,swap__reference} + + @since version 1.0.0 */ void swap(reference other) noexcept ( std::is_nothrow_move_constructible::value and @@ -4016,23 +4563,28 @@ class basic_json @param[in,out] other array to exchange the contents with - @throw std::domain_error when JSON value is not an array + @throw std::domain_error when JSON value is not an array; example: `"cannot + use swap() with string"` @complexity Constant. @liveexample{The example below shows how JSON values can be swapped.,swap__array_t} + + @since version 1.0.0 */ void swap(array_t& other) { // swap only works for arrays - if (m_type != value_t::array) + if (is_array()) + { + assert(m_value.array != nullptr); + std::swap(*(m_value.array), other); + } + else { throw std::domain_error("cannot use swap() with " + type_name()); } - - // swap arrays - std::swap(*(m_value.array), other); } /*! @@ -4045,23 +4597,28 @@ class basic_json @param[in,out] other object to exchange the contents with - @throw std::domain_error when JSON value is not an object + @throw std::domain_error when JSON value is not an object; example: + `"cannot use swap() with string"` @complexity Constant. @liveexample{The example below shows how JSON values can be swapped.,swap__object_t} + + @since version 1.0.0 */ void swap(object_t& other) { // swap only works for objects - if (m_type != value_t::object) + if (is_object()) + { + assert(m_value.object != nullptr); + std::swap(*(m_value.object), other); + } + else { throw std::domain_error("cannot use swap() with " + type_name()); } - - // swap objects - std::swap(*(m_value.object), other); } /*! @@ -4074,23 +4631,28 @@ class basic_json @param[in,out] other string to exchange the contents with - @throw std::domain_error when JSON value is not a string + @throw std::domain_error when JSON value is not a string; example: `"cannot + use swap() with boolean"` @complexity Constant. @liveexample{The example below shows how JSON values can be swapped.,swap__string_t} + + @since version 1.0.0 */ void swap(string_t& other) { // swap only works for strings - if (m_type != value_t::string) + if (is_string()) + { + assert(m_value.string != nullptr); + std::swap(*(m_value.string), other); + } + else { throw std::domain_error("cannot use swap() with " + type_name()); } - - // swap strings - std::swap(*(m_value.string), other); } /// @} @@ -4110,6 +4672,8 @@ class basic_json 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 */ friend bool operator<(const value_t lhs, const value_t rhs) { @@ -4154,6 +4718,8 @@ class basic_json @liveexample{The example demonstrates comparing several JSON types.,operator__equal} + + @since version 1.0.0 */ friend bool operator==(const_reference lhs, const_reference rhs) noexcept { @@ -4164,22 +4730,44 @@ class basic_json { switch (lhs_type) { - case (value_t::array): + case value_t::array: + { + assert(lhs.m_value.array != nullptr); + assert(rhs.m_value.array != nullptr); return *lhs.m_value.array == *rhs.m_value.array; - case (value_t::object): + } + case value_t::object: + { + assert(lhs.m_value.object != nullptr); + assert(rhs.m_value.object != nullptr); return *lhs.m_value.object == *rhs.m_value.object; - case (value_t::null): + } + case value_t::null: + { return true; - case (value_t::string): + } + case value_t::string: + { + assert(lhs.m_value.string != nullptr); + assert(rhs.m_value.string != nullptr); return *lhs.m_value.string == *rhs.m_value.string; - case (value_t::boolean): + } + case value_t::boolean: + { return lhs.m_value.boolean == rhs.m_value.boolean; - case (value_t::number_integer): + } + case value_t::number_integer: + { return lhs.m_value.number_integer == rhs.m_value.number_integer; - case (value_t::number_float): + } + case value_t::number_float: + { return approx(lhs.m_value.number_float, rhs.m_value.number_float); - case (value_t::discarded): + } + default: + { return false; + } } } else if (lhs_type == value_t::number_integer and rhs_type == value_t::number_float) @@ -4210,6 +4798,8 @@ class basic_json @liveexample{The example compares several JSON types to the null pointer. ,operator__equal__nullptr_t} + + @since version 1.0.0 */ friend bool operator==(const_reference v, std::nullptr_t) noexcept { @@ -4238,6 +4828,8 @@ class basic_json @liveexample{The example demonstrates comparing several JSON types.,operator__notequal} + + @since version 1.0.0 */ friend bool operator!=(const_reference lhs, const_reference rhs) noexcept { @@ -4259,6 +4851,8 @@ class basic_json @liveexample{The example compares several JSON types to the null pointer. ,operator__notequal__nullptr_t} + + @since version 1.0.0 */ friend bool operator!=(const_reference v, std::nullptr_t) noexcept { @@ -4295,6 +4889,8 @@ class basic_json @liveexample{The example demonstrates comparing several JSON types.,operator__less} + + @since version 1.0.0 */ friend bool operator<(const_reference lhs, const_reference rhs) noexcept { @@ -4305,22 +4901,44 @@ class basic_json { switch (lhs_type) { - case (value_t::array): + case value_t::array: + { + assert(lhs.m_value.array != nullptr); + assert(rhs.m_value.array != nullptr); return *lhs.m_value.array < *rhs.m_value.array; - case (value_t::object): + } + case value_t::object: + { + assert(lhs.m_value.object != nullptr); + assert(rhs.m_value.object != nullptr); return *lhs.m_value.object < *rhs.m_value.object; - case (value_t::null): + } + case value_t::null: + { return false; - case (value_t::string): + } + case value_t::string: + { + assert(lhs.m_value.string != nullptr); + assert(rhs.m_value.string != nullptr); return *lhs.m_value.string < *rhs.m_value.string; - case (value_t::boolean): + } + case value_t::boolean: + { return lhs.m_value.boolean < rhs.m_value.boolean; - case (value_t::number_integer): + } + case value_t::number_integer: + { return lhs.m_value.number_integer < rhs.m_value.number_integer; - case (value_t::number_float): + } + case value_t::number_float: + { return lhs.m_value.number_float < rhs.m_value.number_float; - case (value_t::discarded): + } + default: + { return false; + } } } else if (lhs_type == value_t::number_integer and rhs_type == value_t::number_float) @@ -4354,6 +4972,8 @@ class basic_json @liveexample{The example demonstrates comparing several JSON types.,operator__greater} + + @since version 1.0.0 */ friend bool operator<=(const_reference lhs, const_reference rhs) noexcept { @@ -4374,6 +4994,8 @@ class basic_json @liveexample{The example demonstrates comparing several JSON types.,operator__lessequal} + + @since version 1.0.0 */ friend bool operator>(const_reference lhs, const_reference rhs) noexcept { @@ -4394,6 +5016,8 @@ class basic_json @liveexample{The example demonstrates comparing several JSON types.,operator__greaterequal} + + @since version 1.0.0 */ friend bool operator>=(const_reference lhs, const_reference rhs) noexcept { @@ -4429,6 +5053,8 @@ class basic_json @liveexample{The example below shows the serialization with different parameters to `width` to adjust the indentation level.,operator_serialize} + + @since version 1.0.0 */ friend std::ostream& operator<<(std::ostream& o, const basic_json& j) { @@ -4477,11 +5103,15 @@ class basic_json LL(1) parser. The complexity can be higher if the parser callback function @a cb has a super-linear complexity. + @note A UTF-8 byte order mark is silently ignored. + @liveexample{The example below demonstrates the parse function with and without callback function.,parse__string__parser_callback_t} - @sa parse(std::istream&, parser_callback_t) for a version that reads from - an input stream + @sa @ref parse(std::istream&, parser_callback_t) for a version that reads + from an input stream + + @since version 1.0.0 */ static basic_json parse(const string_t& s, parser_callback_t cb = nullptr) { @@ -4502,17 +5132,24 @@ class basic_json LL(1) parser. The complexity can be higher if the parser callback function @a cb has a super-linear complexity. + @note A UTF-8 byte order mark is silently ignored. + @liveexample{The example below demonstrates the parse function with and without callback function.,parse__istream__parser_callback_t} - @sa parse(const string_t&, parser_callback_t) for a version that reads + @sa @ref parse(const string_t&, parser_callback_t) for a version that reads from a string + + @since version 1.0.0 */ static basic_json parse(std::istream& i, parser_callback_t cb = nullptr) { return parser(i, cb).parse(); } + /*! + @copydoc parse(std::istream&, parser_callback_t) + */ static basic_json parse(std::istream&& i, parser_callback_t cb = nullptr) { return parser(i, cb).parse(); @@ -4531,11 +5168,15 @@ class basic_json @complexity Linear in the length of the input. The parser is a predictive LL(1) parser. + @note A UTF-8 byte order mark is silently ignored. + @liveexample{The example below shows how a JSON value is constructed by reading a serialization from a stream.,operator_deserialize} @sa parse(std::istream&, parser_callback_t) for a variant with a parser callback function to filter values while parsing + + @since version 1.0.0 */ friend std::istream& operator<<(basic_json& j, std::istream& i) { @@ -4566,40 +5207,20 @@ class basic_json { switch (m_type) { - case (value_t::null): - { + case value_t::null: return "null"; - } - - case (value_t::object): - { + case value_t::object: return "object"; - } - - case (value_t::array): - { + case value_t::array: return "array"; - } - - case (value_t::string): - { + case value_t::string: return "string"; - } - - case (value_t::boolean): - { + case value_t::boolean: return "boolean"; - } - - case (value_t::discarded): - { + case value_t::discarded: return "discarded"; - } - default: - { return "number"; - } } } @@ -4736,11 +5357,21 @@ class basic_json { if (c >= 0x00 and c <= 0x1f) { + // convert a number 0..15 to its hex representation (0..f) + auto hexify = [](const char v) -> char + { + return (v < 10) ? ('0' + v) : ('a' + v - 10); + }; + // print character c as \uxxxx - sprintf(&result[pos + 1], "u%04x", int(c)); - pos += 6; - // overwrite trailing null character - result[pos] = '\\'; + for (const char m : + { 'u', '0', '0', hexify(c >> 4), hexify(c & 0x0f) + }) + { + result[++pos] = m; + } + + ++pos; } else { @@ -4759,12 +5390,12 @@ class basic_json @brief internal implementation of the serialization function This function is called by the public member function dump and organizes - the serializaion internally. The indentation level is propagated as + the serialization internally. The indentation level is propagated as additional parameter. In case of arrays and objects, the function is called recursively. Note that - strings and object keys are escaped using escape_string() - - integer numbers are converted implictly via operator<< + - integer numbers are converted implicitly via operator<< - floating-point numbers are converted to a string using "%g" format @param[out] o stream to write to @@ -4772,7 +5403,9 @@ class basic_json @param[in] indent_step the indent level @param[in] current_indent the current indent level (only used internally) */ - void dump(std::ostream& o, const bool pretty_print, const unsigned int indent_step, + void dump(std::ostream& o, + const bool pretty_print, + const unsigned int indent_step, const unsigned int current_indent = 0) const { // variable to hold indentation for recursive calls @@ -4780,8 +5413,10 @@ class basic_json switch (m_type) { - case (value_t::object): + case value_t::object: { + assert(m_value.object != nullptr); + if (m_value.object->empty()) { o << "{}"; @@ -4820,8 +5455,10 @@ class basic_json return; } - case (value_t::array): + case value_t::array: { + assert(m_value.array != nullptr); + if (m_value.array->empty()) { o << "[]"; @@ -4858,25 +5495,26 @@ class basic_json return; } - case (value_t::string): + case value_t::string: { + assert(m_value.string != nullptr); o << string_t("\"") << escape_string(*m_value.string) << "\""; return; } - case (value_t::boolean): + case value_t::boolean: { o << (m_value.boolean ? "true" : "false"); return; } - case (value_t::number_integer): + case value_t::number_integer: { o << m_value.number_integer; return; } - case (value_t::number_float): + case value_t::number_float: { // 15 digits of precision allows round-trip IEEE 754 // string->double->string; to be safe, we read this value from @@ -4885,13 +5523,13 @@ class basic_json return; } - case (value_t::discarded): + case value_t::discarded: { o << ""; return; } - default: + case value_t::null: { o << "null"; return; @@ -4969,7 +5607,7 @@ class basic_json static constexpr difference_type end_value = begin_value + 1; /// iterator as signed integer type - difference_type m_it = std::numeric_limits::min(); + difference_type m_it = std::numeric_limits::denorm_min(); }; /*! @@ -4994,6 +5632,102 @@ class basic_json {} }; + /// proxy class for the iterator_wrapper functions + template + class iteration_proxy + { + private: + /// helper class for iteration + class iteration_proxy_internal + { + private: + /// the iterator + IteratorType anchor; + /// an index for arrays (used to create key names) + size_t array_index = 0; + + public: + iteration_proxy_internal(IteratorType it) + : anchor(it) + {} + + /// dereference operator (needed for range-based for) + iteration_proxy_internal& operator*() + { + return *this; + } + + /// increment operator (needed for range-based for) + iteration_proxy_internal& operator++() + { + ++anchor; + ++array_index; + + return *this; + } + + /// inequality operator (needed for range-based for) + bool operator!= (const iteration_proxy_internal& o) const + { + return anchor != o.anchor; + } + + /// return key of the iterator + typename basic_json::string_t key() const + { + assert(anchor.m_object != nullptr); + + switch (anchor.m_object->type()) + { + // use integer array index as key + case value_t::array: + { + return std::to_string(array_index); + } + + // use key from the object + case value_t::object: + { + return anchor.key(); + } + + // use an empty key for all primitive types + default: + { + return ""; + } + } + } + + /// return value of the iterator + typename IteratorType::reference value() const + { + return anchor.value(); + } + }; + + /// the container to iterate + typename IteratorType::reference container; + + public: + /// construct iteration proxy from a container + iteration_proxy(typename IteratorType::reference cont) + : container(cont) + {} + + /// return iterator begin (needed for range-based for) + iteration_proxy_internal begin() + { + return iteration_proxy_internal(container.begin()); + } + + /// return iterator end (needed for range-based for) + iteration_proxy_internal end() + { + return iteration_proxy_internal(container.end()); + } + }; + public: /*! @brief a const random access iterator for the @ref basic_json class @@ -5005,6 +5739,8 @@ class basic_json - [RandomAccessIterator](http://en.cppreference.com/w/cpp/concept/RandomAccessIterator): The iterator that can be moved to point (forward and backward) to any element in constant time. + + @since version 1.0.0 */ class const_iterator : public std::iterator { @@ -5029,18 +5765,22 @@ class basic_json /// constructor for a given JSON instance const_iterator(pointer object) : m_object(object) { + assert(m_object != nullptr); + switch (m_object->m_type) { - case (basic_json::value_t::object): + case basic_json::value_t::object: { m_it.object_iterator = typename object_t::iterator(); break; } - case (basic_json::value_t::array): + + case basic_json::value_t::array: { m_it.array_iterator = typename array_t::iterator(); break; } + default: { m_it.primitive_iterator = primitive_iterator_t(); @@ -5052,15 +5792,17 @@ class basic_json /// copy constructor given a nonconst iterator const_iterator(const iterator& other) : m_object(other.m_object) { + assert(m_object != nullptr); + switch (m_object->m_type) { - case (basic_json::value_t::object): + case basic_json::value_t::object: { m_it.object_iterator = other.m_it.object_iterator; break; } - case (basic_json::value_t::array): + case basic_json::value_t::array: { m_it.array_iterator = other.m_it.array_iterator; break; @@ -5096,21 +5838,25 @@ class basic_json /// set the iterator to the first value void set_begin() { + assert(m_object != nullptr); + switch (m_object->m_type) { - case (basic_json::value_t::object): + case basic_json::value_t::object: { + assert(m_object->m_value.object != nullptr); m_it.object_iterator = m_object->m_value.object->begin(); break; } - case (basic_json::value_t::array): + case basic_json::value_t::array: { + assert(m_object->m_value.array != nullptr); m_it.array_iterator = m_object->m_value.array->begin(); break; } - case (basic_json::value_t::null): + case basic_json::value_t::null: { // set to end so begin()==end() is true: null is empty m_it.primitive_iterator.set_end(); @@ -5128,16 +5874,20 @@ class basic_json /// set the iterator past the last value void set_end() { + assert(m_object != nullptr); + switch (m_object->m_type) { - case (basic_json::value_t::object): + case basic_json::value_t::object: { + assert(m_object->m_value.object != nullptr); m_it.object_iterator = m_object->m_value.object->end(); break; } - case (basic_json::value_t::array): + case basic_json::value_t::array: { + assert(m_object->m_value.array != nullptr); m_it.array_iterator = m_object->m_value.array->end(); break; } @@ -5154,19 +5904,25 @@ class basic_json /// return a reference to the value pointed to by the iterator reference operator*() const { + assert(m_object != nullptr); + switch (m_object->m_type) { - case (basic_json::value_t::object): + case basic_json::value_t::object: { + assert(m_object->m_value.object); + assert(m_it.object_iterator != m_object->m_value.object->end()); return m_it.object_iterator->second; } - case (basic_json::value_t::array): + case basic_json::value_t::array: { + assert(m_object->m_value.array); + assert(m_it.array_iterator != m_object->m_value.array->end()); return *m_it.array_iterator; } - case (basic_json::value_t::null): + case basic_json::value_t::null: { throw std::out_of_range("cannot get value"); } @@ -5188,15 +5944,21 @@ class basic_json /// dereference the iterator pointer operator->() const { + assert(m_object != nullptr); + switch (m_object->m_type) { - case (basic_json::value_t::object): + case basic_json::value_t::object: { + assert(m_object->m_value.object); + assert(m_it.object_iterator != m_object->m_value.object->end()); return &(m_it.object_iterator->second); } - case (basic_json::value_t::array): + case basic_json::value_t::array: { + assert(m_object->m_value.array); + assert(m_it.array_iterator != m_object->m_value.array->end()); return &*m_it.array_iterator; } @@ -5219,22 +5981,23 @@ class basic_json { auto result = *this; ++(*this); - return result; } /// pre-increment (++it) const_iterator& operator++() { + assert(m_object != nullptr); + switch (m_object->m_type) { - case (basic_json::value_t::object): + case basic_json::value_t::object: { ++m_it.object_iterator; break; } - case (basic_json::value_t::array): + case basic_json::value_t::array: { ++m_it.array_iterator; break; @@ -5255,22 +6018,23 @@ class basic_json { auto result = *this; --(*this); - return result; } /// pre-decrement (--it) const_iterator& operator--() { + assert(m_object != nullptr); + switch (m_object->m_type) { - case (basic_json::value_t::object): + case basic_json::value_t::object: { --m_it.object_iterator; break; } - case (basic_json::value_t::array): + case basic_json::value_t::array: { --m_it.array_iterator; break; @@ -5295,14 +6059,16 @@ class basic_json throw std::domain_error("cannot compare iterators of different containers"); } + assert(m_object != nullptr); + switch (m_object->m_type) { - case (basic_json::value_t::object): + case basic_json::value_t::object: { return (m_it.object_iterator == other.m_it.object_iterator); } - case (basic_json::value_t::array): + case basic_json::value_t::array: { return (m_it.array_iterator == other.m_it.array_iterator); } @@ -5329,14 +6095,16 @@ class basic_json throw std::domain_error("cannot compare iterators of different containers"); } + assert(m_object != nullptr); + switch (m_object->m_type) { - case (basic_json::value_t::object): + case basic_json::value_t::object: { - throw std::domain_error("cannot use operator< for object iterators"); + throw std::domain_error("cannot compare order of object iterators"); } - case (basic_json::value_t::array): + case basic_json::value_t::array: { return (m_it.array_iterator < other.m_it.array_iterator); } @@ -5369,14 +6137,16 @@ class basic_json /// add to iterator const_iterator& operator+=(difference_type i) { + assert(m_object != nullptr); + switch (m_object->m_type) { - case (basic_json::value_t::object): + case basic_json::value_t::object: { - throw std::domain_error("cannot use operator+= for object iterators"); + throw std::domain_error("cannot use offsets with object iterators"); } - case (basic_json::value_t::array): + case basic_json::value_t::array: { m_it.array_iterator += i; break; @@ -5417,14 +6187,16 @@ class basic_json /// return difference difference_type operator-(const const_iterator& other) const { + assert(m_object != nullptr); + switch (m_object->m_type) { - case (basic_json::value_t::object): + case basic_json::value_t::object: { - throw std::domain_error("cannot use operator- for object iterators"); + throw std::domain_error("cannot use offsets with object iterators"); } - case (basic_json::value_t::array): + case basic_json::value_t::array: { return m_it.array_iterator - other.m_it.array_iterator; } @@ -5439,19 +6211,21 @@ class basic_json /// access to successor reference operator[](difference_type n) const { + assert(m_object != nullptr); + switch (m_object->m_type) { - case (basic_json::value_t::object): + case basic_json::value_t::object: { throw std::domain_error("cannot use operator[] for object iterators"); } - case (basic_json::value_t::array): + case basic_json::value_t::array: { return *(m_it.array_iterator + n); } - case (basic_json::value_t::null): + case basic_json::value_t::null: { throw std::out_of_range("cannot get value"); } @@ -5473,17 +6247,15 @@ class basic_json /// return the key of an object iterator typename object_t::key_type key() const { - switch (m_object->m_type) - { - case (basic_json::value_t::object): - { - return m_it.object_iterator->first; - } + assert(m_object != nullptr); - default: - { - throw std::domain_error("cannot use key() for non-object iterators"); - } + if (m_object->is_object()) + { + return m_it.object_iterator->first; + } + else + { + throw std::domain_error("cannot use key() for non-object iterators"); } } @@ -5509,6 +6281,8 @@ class basic_json element in constant time. - [OutputIterator](http://en.cppreference.com/w/cpp/concept/OutputIterator): It is possible to write to the pointed-to element. + + @since version 1.0.0 */ class iterator : public const_iterator { @@ -5521,7 +6295,8 @@ class basic_json iterator() = default; /// constructor for a given JSON instance - iterator(pointer object) noexcept : base_iterator(object) + iterator(pointer object) noexcept + : base_iterator(object) {} /// copy constructor @@ -5645,6 +6420,8 @@ class basic_json - [OutputIterator](http://en.cppreference.com/w/cpp/concept/OutputIterator): It is possible to write to the pointed-to element (only if @a Base is @ref iterator). + + @since version 1.0.0 */ template class json_reverse_iterator : public std::reverse_iterator @@ -5657,10 +6434,13 @@ class basic_json /// create reverse iterator from iterator json_reverse_iterator(const typename base_iterator::iterator_type& it) - : base_iterator(it) {} + : base_iterator(it) + {} /// create reverse iterator from base class - json_reverse_iterator(const base_iterator& it) : base_iterator(it) {} + json_reverse_iterator(const base_iterator& it) + : base_iterator(it) + {} /// post-increment (it++) json_reverse_iterator operator++(int) @@ -5738,106 +6518,6 @@ class basic_json } }; - /*! - @brief wrapper to access iterator member functions in range-based for - - This class allows to access @ref key() and @ref value() during range-based - for loops. In these loops, a reference to the JSON values is returned, so - there is no access to the underlying iterator. - */ - class iterator_wrapper - { - private: - /// the container to iterate - basic_json& container; - /// the type of the iterator to use while iteration - using json_iterator = decltype(std::begin(container)); - - /// internal iterator wrapper - class iterator_wrapper_internal - { - private: - /// the iterator - json_iterator anchor; - /// an index for arrays - size_t array_index = 0; - - public: - /// construct wrapper given an iterator - iterator_wrapper_internal(json_iterator i) : anchor(i) - {} - - /// dereference operator (needed for range-based for) - iterator_wrapper_internal& operator*() - { - return *this; - } - - /// increment operator (needed for range-based for) - iterator_wrapper_internal& operator++() - { - ++anchor; - ++array_index; - - return *this; - } - - /// inequality operator (needed for range-based for) - bool operator!= (const iterator_wrapper_internal& o) - { - return anchor != o.anchor; - } - - /// return key of the iterator - typename basic_json::string_t key() const - { - switch (anchor.m_object->type()) - { - /// use integer array index as key - case (value_t::array): - { - return std::to_string(array_index); - } - - /// use key from the object - case (value_t::object): - { - return anchor.key(); - } - - /// use an empty key for all primitive types - default: - { - return ""; - } - } - } - - /// return value of the iterator - typename json_iterator::reference value() const - { - return anchor.value(); - } - }; - - public: - /// construct iterator wrapper from a container - iterator_wrapper(basic_json& cont) - : container(cont) - {} - - /// return iterator begin (needed for range-based for) - iterator_wrapper_internal begin() - { - return iterator_wrapper_internal(container.begin()); - } - - /// return iterator end (needed for range-based for) - iterator_wrapper_internal end() - { - return iterator_wrapper_internal(container.end()); - } - }; private: ////////////////////// @@ -5861,8 +6541,8 @@ class basic_json 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_number, ///< a number - use get_number() for actual value + value_string, ///< a string -- use get_string() for actual value + value_number, ///< a number -- use get_number() for actual value begin_array, ///< the character for array begin "[" begin_object, ///< the character for object begin "{" end_array, ///< the character for array end "]" @@ -5881,14 +6561,19 @@ class basic_json : m_stream(nullptr), m_buffer(s) { m_content = reinterpret_cast(s.c_str()); + assert(m_content != nullptr); m_start = m_cursor = m_content; m_limit = m_content + s.size(); } + + /// constructor with a given stream explicit lexer(std::istream* s) noexcept : m_stream(s), m_buffer() { + assert(m_stream != nullptr); getline(*m_stream, m_buffer); m_content = reinterpret_cast(m_buffer.c_str()); + assert(m_content != nullptr); m_start = m_cursor = m_content; m_limit = m_content + m_buffer.size(); } @@ -5896,7 +6581,7 @@ class basic_json /// default constructor lexer() = default; - // switch of unwanted functions + // switch off unwanted functions lexer(const lexer&) = delete; lexer operator=(const lexer&) = delete; @@ -5905,9 +6590,13 @@ class basic_json @param[in] codepoint1 the code point (can be high surrogate) @param[in] codepoint2 the code point (can be low surrogate or 0) + @return string representation of the code point - @throw std::out_of_range if code point is >0x10ffff - @throw std::invalid_argument if the low surrogate is invalid + + @throw std::out_of_range if code point is >0x10ffff; example: `"code + points above 0x10FFFF are invalid"` + @throw std::invalid_argument if the low surrogate is invalid; example: + `""missing or wrong low surrogate""` @see */ @@ -5931,7 +6620,7 @@ class basic_json // 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 substract with: + // in the result so we have to subtract with: // (0xD800 << 10) + DC00 - 0x10000 = 0x35FDC00 - 0x35FDC00; } @@ -5975,39 +6664,44 @@ class basic_json return result; } - /// return name of values of type token_type + /// return name of values of type token_type (only used for errors) static std::string token_type_name(token_type t) { switch (t) { - case (token_type::uninitialized): + case token_type::uninitialized: return ""; - case (token_type::literal_true): + case token_type::literal_true: return "true literal"; - case (token_type::literal_false): + case token_type::literal_false: return "false literal"; - case (token_type::literal_null): + case token_type::literal_null: return "null literal"; - case (token_type::value_string): + case token_type::value_string: return "string literal"; - case (token_type::value_number): + case token_type::value_number: 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::end_of_input): - return ""; - default: + 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 ""; + case token_type::end_of_input: + return "end of input"; + default: + { + // catch non-enum values + return "unknown token"; // LCOV_EXCL_LINE + } } } @@ -6028,6 +6722,7 @@ class basic_json // remember the begin of the token m_start = m_cursor; + assert(m_start != nullptr); { @@ -6068,13 +6763,12 @@ class basic_json 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, }; - if ((m_limit - m_cursor) < 5) { yyfill(); // LCOV_EXCL_LINE; } yych = *m_cursor; - if (yych <= '9') + if (yych <= ':') { if (yych <= ' ') { @@ -6082,11 +6776,11 @@ class basic_json { if (yych <= 0x00) { - goto basic_json_parser_27; + goto basic_json_parser_28; } if (yych <= 0x08) { - goto basic_json_parser_29; + goto basic_json_parser_30; } if (yych >= '\n') { @@ -6101,7 +6795,7 @@ class basic_json } if (yych <= 0x1F) { - goto basic_json_parser_29; + goto basic_json_parser_30; } } } @@ -6111,86 +6805,100 @@ class basic_json { if (yych == '"') { - goto basic_json_parser_26; + goto basic_json_parser_27; } if (yych <= '+') { - goto basic_json_parser_29; + goto basic_json_parser_30; } - goto basic_json_parser_14; + goto basic_json_parser_16; } else { - if (yych <= '-') - { - goto basic_json_parser_22; - } if (yych <= '/') { - goto basic_json_parser_29; + if (yych <= '-') + { + goto basic_json_parser_23; + } + goto basic_json_parser_30; } - if (yych <= '0') + else { - goto basic_json_parser_23; + if (yych <= '0') + { + goto basic_json_parser_24; + } + if (yych <= '9') + { + goto basic_json_parser_26; + } + goto basic_json_parser_18; } - goto basic_json_parser_25; } } } else { - if (yych <= 'm') + if (yych <= 'n') { - if (yych <= '\\') + if (yych <= ']') { - if (yych <= ':') - { - goto basic_json_parser_16; - } if (yych == '[') - { - goto basic_json_parser_6; - } - goto basic_json_parser_29; - } - else - { - if (yych <= ']') { goto basic_json_parser_8; } + if (yych <= '\\') + { + goto basic_json_parser_30; + } + goto basic_json_parser_10; + } + else + { if (yych == 'f') { - goto basic_json_parser_21; + goto basic_json_parser_22; } - goto basic_json_parser_29; + if (yych <= 'm') + { + goto basic_json_parser_30; + } + goto basic_json_parser_20; } } else { - if (yych <= 'z') + if (yych <= '{') { - if (yych <= 'n') - { - goto basic_json_parser_18; - } if (yych == 't') { - goto basic_json_parser_20; + goto basic_json_parser_21; } - goto basic_json_parser_29; + if (yych <= 'z') + { + goto basic_json_parser_30; + } + goto basic_json_parser_12; } else { - if (yych <= '{') + if (yych <= '}') { - goto basic_json_parser_10; + if (yych <= '|') + { + goto basic_json_parser_30; + } + goto basic_json_parser_14; } - if (yych == '}') + else { - goto basic_json_parser_12; + if (yych == 0xEF) + { + goto basic_json_parser_6; + } + goto basic_json_parser_30; } - goto basic_json_parser_29; } } } @@ -6216,154 +6924,162 @@ basic_json_parser_5: } goto basic_json_parser_3; basic_json_parser_6: - ++m_cursor; + yyaccept = 0; + yych = *(m_marker = ++m_cursor); + if (yych == 0xBB) { - return token_type::begin_array; + goto basic_json_parser_64; + } +basic_json_parser_7: + { + return token_type::parse_error; } basic_json_parser_8: ++m_cursor; { - return token_type::end_array; + return token_type::begin_array; } basic_json_parser_10: ++m_cursor; { - return token_type::begin_object; + return token_type::end_array; } basic_json_parser_12: ++m_cursor; { - return token_type::end_object; + return token_type::begin_object; } basic_json_parser_14: ++m_cursor; { - return token_type::value_separator; + return token_type::end_object; } basic_json_parser_16: ++m_cursor; { - return token_type::name_separator; + return token_type::value_separator; } basic_json_parser_18: - yyaccept = 0; - yych = *(m_marker = ++m_cursor); - if (yych == 'u') + ++m_cursor; { - goto basic_json_parser_59; - } -basic_json_parser_19: - { - return token_type::parse_error; + return token_type::name_separator; } basic_json_parser_20: + yyaccept = 0; + yych = *(m_marker = ++m_cursor); + if (yych == 'u') + { + goto basic_json_parser_60; + } + goto basic_json_parser_7; +basic_json_parser_21: yyaccept = 0; yych = *(m_marker = ++m_cursor); if (yych == 'r') { - goto basic_json_parser_55; + goto basic_json_parser_56; } - goto basic_json_parser_19; -basic_json_parser_21: + goto basic_json_parser_7; +basic_json_parser_22: yyaccept = 0; yych = *(m_marker = ++m_cursor); if (yych == 'a') { - goto basic_json_parser_50; + goto basic_json_parser_51; } - goto basic_json_parser_19; -basic_json_parser_22: + goto basic_json_parser_7; +basic_json_parser_23: yych = *++m_cursor; if (yych <= '/') { - goto basic_json_parser_19; + goto basic_json_parser_7; } if (yych <= '0') { - goto basic_json_parser_49; + goto basic_json_parser_50; } if (yych <= '9') { - goto basic_json_parser_40; + goto basic_json_parser_41; } - goto basic_json_parser_19; -basic_json_parser_23: + goto basic_json_parser_7; +basic_json_parser_24: yyaccept = 1; yych = *(m_marker = ++m_cursor); if (yych <= 'D') { if (yych == '.') { - goto basic_json_parser_42; + goto basic_json_parser_43; } } else { if (yych <= 'E') { - goto basic_json_parser_43; + goto basic_json_parser_44; } if (yych == 'e') { - goto basic_json_parser_43; + goto basic_json_parser_44; } } -basic_json_parser_24: +basic_json_parser_25: { return token_type::value_number; } -basic_json_parser_25: +basic_json_parser_26: yyaccept = 1; yych = *(m_marker = ++m_cursor); - goto basic_json_parser_41; -basic_json_parser_26: + goto basic_json_parser_42; +basic_json_parser_27: yyaccept = 0; yych = *(m_marker = ++m_cursor); if (yych <= 0x0F) { - goto basic_json_parser_19; + goto basic_json_parser_7; } - goto basic_json_parser_31; -basic_json_parser_27: + goto basic_json_parser_32; +basic_json_parser_28: ++m_cursor; { return token_type::end_of_input; } -basic_json_parser_29: - yych = *++m_cursor; - goto basic_json_parser_19; basic_json_parser_30: + yych = *++m_cursor; + goto basic_json_parser_7; +basic_json_parser_31: ++m_cursor; if (m_limit <= m_cursor) { yyfill(); // LCOV_EXCL_LINE; } yych = *m_cursor; -basic_json_parser_31: +basic_json_parser_32: if (yybm[0 + yych] & 64) { - goto basic_json_parser_30; + goto basic_json_parser_31; } if (yych <= 0x0F) { - goto basic_json_parser_32; + goto basic_json_parser_33; } if (yych <= '"') { - goto basic_json_parser_34; + goto basic_json_parser_35; } - goto basic_json_parser_33; -basic_json_parser_32: + goto basic_json_parser_34; +basic_json_parser_33: m_cursor = m_marker; if (yyaccept == 0) { - goto basic_json_parser_19; + goto basic_json_parser_7; } else { - goto basic_json_parser_24; + goto basic_json_parser_25; } -basic_json_parser_33: +basic_json_parser_34: ++m_cursor; if (m_limit <= m_cursor) { @@ -6376,13 +7092,13 @@ basic_json_parser_33: { if (yych == '"') { - goto basic_json_parser_30; + goto basic_json_parser_31; } if (yych <= '.') { - goto basic_json_parser_32; + goto basic_json_parser_33; } - goto basic_json_parser_30; + goto basic_json_parser_31; } else { @@ -6390,17 +7106,17 @@ basic_json_parser_33: { if (yych <= '[') { - goto basic_json_parser_32; + goto basic_json_parser_33; } - goto basic_json_parser_30; + goto basic_json_parser_31; } else { if (yych == 'b') { - goto basic_json_parser_30; + goto basic_json_parser_31; } - goto basic_json_parser_32; + goto basic_json_parser_33; } } } @@ -6410,13 +7126,13 @@ basic_json_parser_33: { if (yych <= 'f') { - goto basic_json_parser_30; + goto basic_json_parser_31; } if (yych == 'n') { - goto basic_json_parser_30; + goto basic_json_parser_31; } - goto basic_json_parser_32; + goto basic_json_parser_33; } else { @@ -6424,62 +7140,29 @@ basic_json_parser_33: { if (yych <= 'r') { - goto basic_json_parser_30; + goto basic_json_parser_31; } - goto basic_json_parser_32; + goto basic_json_parser_33; } else { if (yych <= 't') { - goto basic_json_parser_30; + goto basic_json_parser_31; } if (yych <= 'u') { - goto basic_json_parser_36; + goto basic_json_parser_37; } - goto basic_json_parser_32; + goto basic_json_parser_33; } } } -basic_json_parser_34: +basic_json_parser_35: ++m_cursor; { return token_type::value_string; } -basic_json_parser_36: - ++m_cursor; - if (m_limit <= m_cursor) - { - yyfill(); // LCOV_EXCL_LINE; - } - yych = *m_cursor; - if (yych <= '@') - { - if (yych <= '/') - { - goto basic_json_parser_32; - } - if (yych >= ':') - { - goto basic_json_parser_32; - } - } - else - { - if (yych <= 'F') - { - goto basic_json_parser_37; - } - if (yych <= '`') - { - goto basic_json_parser_32; - } - if (yych >= 'g') - { - goto basic_json_parser_32; - } - } basic_json_parser_37: ++m_cursor; if (m_limit <= m_cursor) @@ -6491,11 +7174,11 @@ basic_json_parser_37: { if (yych <= '/') { - goto basic_json_parser_32; + goto basic_json_parser_33; } if (yych >= ':') { - goto basic_json_parser_32; + goto basic_json_parser_33; } } else @@ -6506,11 +7189,11 @@ basic_json_parser_37: } if (yych <= '`') { - goto basic_json_parser_32; + goto basic_json_parser_33; } if (yych >= 'g') { - goto basic_json_parser_32; + goto basic_json_parser_33; } } basic_json_parser_38: @@ -6524,11 +7207,11 @@ basic_json_parser_38: { if (yych <= '/') { - goto basic_json_parser_32; + goto basic_json_parser_33; } if (yych >= ':') { - goto basic_json_parser_32; + goto basic_json_parser_33; } } else @@ -6539,11 +7222,11 @@ basic_json_parser_38: } if (yych <= '`') { - goto basic_json_parser_32; + goto basic_json_parser_33; } if (yych >= 'g') { - goto basic_json_parser_32; + goto basic_json_parser_33; } } basic_json_parser_39: @@ -6557,31 +7240,64 @@ basic_json_parser_39: { if (yych <= '/') { - goto basic_json_parser_32; + goto basic_json_parser_33; } - if (yych <= '9') + if (yych >= ':') { - goto basic_json_parser_30; + goto basic_json_parser_33; } - goto basic_json_parser_32; } else { if (yych <= 'F') { - goto basic_json_parser_30; + goto basic_json_parser_40; } if (yych <= '`') { - goto basic_json_parser_32; + goto basic_json_parser_33; + } + if (yych >= 'g') + { + goto basic_json_parser_33; + } + } +basic_json_parser_40: + ++m_cursor; + if (m_limit <= m_cursor) + { + yyfill(); // LCOV_EXCL_LINE; + } + yych = *m_cursor; + if (yych <= '@') + { + if (yych <= '/') + { + goto basic_json_parser_33; + } + if (yych <= '9') + { + goto basic_json_parser_31; + } + goto basic_json_parser_33; + } + else + { + if (yych <= 'F') + { + goto basic_json_parser_31; + } + if (yych <= '`') + { + goto basic_json_parser_33; } if (yych <= 'f') { - goto basic_json_parser_30; + goto basic_json_parser_31; } - goto basic_json_parser_32; + goto basic_json_parser_33; } -basic_json_parser_40: +basic_json_parser_41: yyaccept = 1; m_marker = ++m_cursor; if ((m_limit - m_cursor) < 3) @@ -6589,77 +7305,77 @@ basic_json_parser_40: yyfill(); // LCOV_EXCL_LINE; } yych = *m_cursor; -basic_json_parser_41: +basic_json_parser_42: if (yybm[0 + yych] & 128) { - goto basic_json_parser_40; + goto basic_json_parser_41; } if (yych <= 'D') { if (yych != '.') { - goto basic_json_parser_24; + goto basic_json_parser_25; } } else { if (yych <= 'E') { - goto basic_json_parser_43; + goto basic_json_parser_44; } if (yych == 'e') { - goto basic_json_parser_43; + goto basic_json_parser_44; } - goto basic_json_parser_24; + goto basic_json_parser_25; } -basic_json_parser_42: +basic_json_parser_43: yych = *++m_cursor; if (yych <= '/') { - goto basic_json_parser_32; + goto basic_json_parser_33; } if (yych <= '9') { - goto basic_json_parser_47; + goto basic_json_parser_48; } - goto basic_json_parser_32; -basic_json_parser_43: + goto basic_json_parser_33; +basic_json_parser_44: yych = *++m_cursor; if (yych <= ',') { if (yych != '+') { - goto basic_json_parser_32; + goto basic_json_parser_33; } } else { if (yych <= '-') { - goto basic_json_parser_44; + goto basic_json_parser_45; } if (yych <= '/') { - goto basic_json_parser_32; + goto basic_json_parser_33; } if (yych <= '9') { - goto basic_json_parser_45; + goto basic_json_parser_46; } - goto basic_json_parser_32; + goto basic_json_parser_33; } -basic_json_parser_44: +basic_json_parser_45: yych = *++m_cursor; if (yych <= '/') { - goto basic_json_parser_32; + goto basic_json_parser_33; } if (yych >= ':') { - goto basic_json_parser_32; + goto basic_json_parser_33; } -basic_json_parser_45: +basic_json_parser_46: ++m_cursor; if (m_limit <= m_cursor) { @@ -6668,14 +7384,14 @@ basic_json_parser_45: yych = *m_cursor; if (yych <= '/') { - goto basic_json_parser_24; + goto basic_json_parser_25; } if (yych <= '9') { - goto basic_json_parser_45; + goto basic_json_parser_46; } - goto basic_json_parser_24; -basic_json_parser_47: + goto basic_json_parser_25; +basic_json_parser_48: yyaccept = 1; m_marker = ++m_cursor; if ((m_limit - m_cursor) < 3) @@ -6687,99 +7403,109 @@ basic_json_parser_47: { if (yych <= '/') { - goto basic_json_parser_24; + goto basic_json_parser_25; } if (yych <= '9') { - goto basic_json_parser_47; + goto basic_json_parser_48; } - goto basic_json_parser_24; + goto basic_json_parser_25; } else { if (yych <= 'E') { - goto basic_json_parser_43; + goto basic_json_parser_44; } if (yych == 'e') { - goto basic_json_parser_43; + goto basic_json_parser_44; } - goto basic_json_parser_24; + goto basic_json_parser_25; } -basic_json_parser_49: +basic_json_parser_50: yyaccept = 1; yych = *(m_marker = ++m_cursor); if (yych <= 'D') { if (yych == '.') { - goto basic_json_parser_42; + goto basic_json_parser_43; } - goto basic_json_parser_24; + goto basic_json_parser_25; } else { if (yych <= 'E') { - goto basic_json_parser_43; + goto basic_json_parser_44; } if (yych == 'e') { - goto basic_json_parser_43; + goto basic_json_parser_44; } - goto basic_json_parser_24; + goto basic_json_parser_25; } -basic_json_parser_50: +basic_json_parser_51: yych = *++m_cursor; if (yych != 'l') { - goto basic_json_parser_32; + goto basic_json_parser_33; } yych = *++m_cursor; if (yych != 's') { - goto basic_json_parser_32; + goto basic_json_parser_33; } yych = *++m_cursor; if (yych != 'e') { - goto basic_json_parser_32; + goto basic_json_parser_33; } ++m_cursor; { return token_type::literal_false; } -basic_json_parser_55: +basic_json_parser_56: yych = *++m_cursor; if (yych != 'u') { - goto basic_json_parser_32; + goto basic_json_parser_33; } yych = *++m_cursor; if (yych != 'e') { - goto basic_json_parser_32; + goto basic_json_parser_33; } ++m_cursor; { return token_type::literal_true; } -basic_json_parser_59: +basic_json_parser_60: yych = *++m_cursor; if (yych != 'l') { - goto basic_json_parser_32; + goto basic_json_parser_33; } yych = *++m_cursor; if (yych != 'l') { - goto basic_json_parser_32; + goto basic_json_parser_33; } ++m_cursor; { return token_type::literal_null; } +basic_json_parser_64: + yych = *++m_cursor; + if (yych != 0xBF) + { + goto basic_json_parser_33; + } + ++m_cursor; + { + return scan(); + } } @@ -6788,7 +7514,7 @@ basic_json_parser_59: /// append data from the stream to the internal buffer void yyfill() noexcept { - if (not m_stream or not * m_stream) + if (m_stream == nullptr or not * m_stream) { return; } @@ -6799,10 +7525,12 @@ basic_json_parser_59: m_buffer.erase(0, static_cast(offset_start)); std::string line; + assert(m_stream != nullptr); std::getline(*m_stream, line); m_buffer += "\n" + line; // add line with newline symbol m_content = reinterpret_cast(m_buffer.c_str()); + assert(m_content != nullptr); m_start = m_content; m_marker = m_start + offset_marker; m_cursor = m_start + offset_cursor; @@ -6812,6 +7540,7 @@ basic_json_parser_59: /// return string representation of last read token string_t get_token() const noexcept { + assert(m_start != nullptr); return string_t(reinterpret_cast(m_start), static_cast(m_cursor - m_start)); } @@ -6915,8 +7644,8 @@ basic_json_parser_59: auto codepoint2 = std::strtoul(std::string(reinterpret_cast (i + 7), 4).c_str(), nullptr, 16); result += to_unicode(codepoint, codepoint2); - // skip the next 11 characters (xxxx\uyyyy) - i += 11; + // skip the next 10 characters (xxxx\uyyyy) + i += 10; } else { @@ -6961,6 +7690,7 @@ basic_json_parser_59: { // conversion typename string_t::value_type* endptr; + assert(m_start != nullptr); const auto float_val = std::strtold(reinterpret_cast(m_start), &endptr); @@ -6971,7 +7701,7 @@ basic_json_parser_59: private: /// optional input stream - std::istream* m_stream; + std::istream* m_stream = nullptr; /// the buffer string_t m_buffer; /// the buffer pointer @@ -6988,6 +7718,8 @@ basic_json_parser_59: /*! @brief syntax analysis + + This class implements a recursive decent parser. */ class parser { @@ -7028,7 +7760,7 @@ basic_json_parser_59: switch (last_token) { - case (lexer::token_type::begin_object): + case lexer::token_type::begin_object: { if (keep and (not callback or (keep = callback(depth++, parse_event_t::object_start, result)))) { @@ -7106,7 +7838,7 @@ basic_json_parser_59: return result; } - case (lexer::token_type::begin_array): + case lexer::token_type::begin_array: { if (keep and (not callback or (keep = callback(depth++, parse_event_t::array_start, result)))) { @@ -7161,14 +7893,14 @@ basic_json_parser_59: return result; } - case (lexer::token_type::literal_null): + case lexer::token_type::literal_null: { get_token(); result.m_type = value_t::null; break; } - case (lexer::token_type::value_string): + case lexer::token_type::value_string: { const auto s = m_lexer.get_string(); get_token(); @@ -7176,7 +7908,7 @@ basic_json_parser_59: break; } - case (lexer::token_type::literal_true): + case lexer::token_type::literal_true: { get_token(); result.m_type = value_t::boolean; @@ -7184,7 +7916,7 @@ basic_json_parser_59: break; } - case (lexer::token_type::literal_false): + case lexer::token_type::literal_false: { get_token(); result.m_type = value_t::boolean; @@ -7192,7 +7924,7 @@ basic_json_parser_59: break; } - case (lexer::token_type::value_number): + case lexer::token_type::value_number: { auto float_val = m_lexer.get_number(); @@ -7210,13 +7942,13 @@ basic_json_parser_59: const auto int_val = static_cast(float_val); if (approx(float_val, static_cast(int_val))) { - // we basic_json not lose precision -> return int + // we would not lose precision -> return int result.m_type = value_t::number_integer; result.m_value = int_val; } else { - // we would lose precision -> returnfloat + // we would lose precision -> return float result.m_type = value_t::number_float; result.m_value = static_cast(float_val); } @@ -7248,10 +7980,10 @@ basic_json_parser_59: { if (t != last_token) { - std::string error_msg = "parse error - unexpected \'"; - error_msg += m_lexer.get_token(); - error_msg += "\' (" + lexer::token_type_name(last_token); - error_msg += "); expected " + lexer::token_type_name(t); + std::string error_msg = "parse error - unexpected "; + error_msg += (last_token == lexer::token_type::parse_error ? ("'" + m_lexer.get_token() + "'") : + lexer::token_type_name(last_token)); + error_msg += "; expected " + lexer::token_type_name(t); throw std::invalid_argument(error_msg); } } @@ -7260,10 +7992,9 @@ basic_json_parser_59: { if (t == last_token) { - std::string error_msg = "parse error - unexpected \'"; - error_msg += m_lexer.get_token(); - error_msg += "\' ("; - error_msg += lexer::token_type_name(last_token) + ")"; + std::string error_msg = "parse error - unexpected "; + error_msg += (last_token == lexer::token_type::parse_error ? ("'" + m_lexer.get_token() + "'") : + lexer::token_type_name(last_token)); throw std::invalid_argument(error_msg); } } @@ -7290,6 +8021,8 @@ basic_json_parser_59: This type is the default specialization of the @ref basic_json class which uses the standard template types. + +@since version 1.0.0 */ using json = basic_json<>; } @@ -7304,6 +8037,8 @@ namespace std { /*! @brief exchanges the values of two JSON objects + +@since version 1.0.0 */ template <> inline void swap(nlohmann::json& j1, @@ -7319,7 +8054,11 @@ inline void swap(nlohmann::json& j1, template <> struct hash { - /// return a hash value for a JSON object + /*! + @brief return a hash value for a JSON object + + @since version 1.0.0 + */ std::size_t operator()(const nlohmann::json& j) const { // a naive hashing via the string representation @@ -7338,11 +8077,12 @@ no parse error occurred. @param[in] s a string representation of a JSON object @return a JSON object + +@since version 1.0.0 */ inline nlohmann::json operator "" _json(const char* s, std::size_t) { - return nlohmann::json::parse(reinterpret_cast - (const_cast(s))); + return nlohmann::json::parse(reinterpret_cast(s)); } #endif diff --git a/src/json.hpp.re2c b/src/json.hpp.re2c index 0ffddd503..a733f0979 100644 --- a/src/json.hpp.re2c +++ b/src/json.hpp.re2c @@ -7,32 +7,32 @@ header-only JSON class. Class @ref nlohmann::basic_json is a good entry point for the documentation. @copyright The code is licensed under the [MIT - License](http://opensource.org/licenses/MIT): -
- Copyright © 2013-2015 Niels Lohmann. -
- 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. + License](http://opensource.org/licenses/MIT): +
+ Copyright © 2013-2016 Niels Lohmann. +
+ 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. @author [Niels Lohmann](http://nlohmann.me) @see https://github.com/nlohmann/json to download the source code + +@version 1.0.0 */ #ifndef NLOHMANN_JSON_HPP @@ -40,6 +40,7 @@ Class @ref nlohmann::basic_json is a good entry point for the documentation. #include #include +#include #include #include #include @@ -73,6 +74,7 @@ Class @ref nlohmann::basic_json is a good entry point for the documentation. /*! @brief namespace for Niels Lohmann @see https://github.com/nlohmann +@since version 1.0.0 */ namespace nlohmann { @@ -80,6 +82,7 @@ namespace nlohmann /*! @brief unnamed namespace with internal helper functions +@since version 1.0.0 */ namespace { @@ -92,9 +95,9 @@ struct has_mapped_type { private: template static char test(typename C::mapped_type*); - template static int test(...); + template static char (&test(...))[2]; public: - enum { value = sizeof(test(0)) == sizeof(char) }; + static constexpr bool value = sizeof(test(0)) == 1; }; /// "equality" comparison for floating point numbers @@ -130,7 +133,7 @@ default) - [MoveConstructible](http://en.cppreference.com/w/cpp/concept/MoveConstructible): A JSON value can be constructed from an rvalue argument. - [CopyConstructible](http://en.cppreference.com/w/cpp/concept/CopyConstructible): - A JSON value can be copy-constrcuted from an lvalue expression. + A JSON value can be copy-constructed from an lvalue expression. - [MoveAssignable](http://en.cppreference.com/w/cpp/concept/MoveAssignable): A JSON value van be assigned from an rvalue argument. - [CopyAssignable](http://en.cppreference.com/w/cpp/concept/CopyAssignable): @@ -168,6 +171,10 @@ default) @endinternal @see RFC 7159 + +@since version 1.0.0 + +@nosubgrouping */ template < template class ObjectType = std::map, @@ -261,10 +268,16 @@ class basic_json > where a name is a string and a value is a string, number, boolean, null, > object, or array. - To store objects in C++, a type is defined by the template parameters @a - ObjectType which chooses the container (e.g., `std::map` or - `std::unordered_map`), @a StringType which chooses the type of the keys or - names, and @a AllocatorType which chooses the allocator to use. + To store objects in C++, a type is defined by the template parameters + described below. + + @tparam ObjectType the container to store objects (e.g., `std::map` or + `std::unordered_map`) + @tparam StringType the type of the keys or names (e.g., `std::string`). The + comparison function `std::less` is used to order elements + inside the container. + @tparam AllocatorType the allocator to use for objects (e.g., + `std::allocator`) #### Default type @@ -314,10 +327,12 @@ class basic_json #### Storage - Objects are stored as pointers in a `basic_json` type. That is, for any + Objects are stored as pointers in a @ref basic_json type. That is, for any access to object values, a pointer of type `object_t*` must be dereferenced. - @sa array_t + @sa @ref array_t -- type for an array value + + @since version 1.0.0 */ using object_t = ObjectType An array is an ordered sequence of zero or more values. - To store objects in C++, a type is defined by the template parameters @a - ArrayType which chooses the container (e.g., `std::vector` or `std::list`) - and @a AllocatorType which chooses the allocator to use. + To store objects in C++, a type is defined by the template parameters + explained below. + + @tparam ArrayType container type to store arrays (e.g., `std::vector` or + `std::list`) + @tparam AllocatorType allocator to use for arrays (e.g., `std::allocator`) #### Default type @@ -359,8 +377,12 @@ class basic_json #### Storage - Arrays are stored as pointers in a `basic_json` type. That is, for any + Arrays are stored as pointers in a @ref basic_json type. That is, for any access to array values, a pointer of type `array_t*` must be dereferenced. + + @sa @ref object_t -- type for an object value + + @since version 1.0.0 */ using array_t = ArrayType>; @@ -370,11 +392,12 @@ class basic_json [RFC 7159](http://rfc7159.net/rfc7159) describes JSON strings as follows: > A string is a sequence of zero or more Unicode characters. - To store objects in C++, a type is defined by the template parameters @a - StringType which chooses the container (e.g., `std::string`) to use. + To store objects in C++, a type is defined by the template parameter + described below. Unicode values are split by the JSON class into byte-sized + characters during deserialization. - Unicode values are split by the JSON class into byte-sized characters - during deserialization. + @tparam StringType the container to store strings (e.g., `std::string`). + Note this container is used for keys/names in objects, see @ref object_t. #### Default type @@ -402,9 +425,11 @@ class basic_json #### Storage - String values are stored as pointers in a `basic_json` type. That is, for - any access to string values, a pointer of type `string_t*` must be + String values are stored as pointers in a @ref basic_json type. That is, + for any access to string values, a pointer of type `string_t*` must be dereferenced. + + @since version 1.0.0 */ using string_t = StringType; @@ -428,7 +453,9 @@ class basic_json #### Storage - Boolean values are stored directly inside a `basic_json` type. + Boolean values are stored directly inside a @ref basic_json type. + + @since version 1.0.0 */ using boolean_t = BooleanType; @@ -492,7 +519,11 @@ class basic_json #### Storage - Integer number values are stored directly inside a `basic_json` type. + Integer number values are stored directly inside a @ref basic_json type. + + @sa @ref number_float_t -- type for number values (floating-point) + + @since version 1.0.0 */ using number_integer_t = NumberIntegerType; @@ -547,12 +578,17 @@ class basic_json This implementation does exactly follow this approach, as it uses double precision floating-point numbers. Note values smaller than - `-1.79769313486232e+308` and values greather than `1.79769313486232e+308` + `-1.79769313486232e+308` and values greater than `1.79769313486232e+308` will be stored as NaN internally and be serialized to `null`. #### Storage - Floating-point number values are stored directly inside a `basic_json` type. + Floating-point number values are stored directly inside a @ref basic_json + type. + + @sa @ref number_integer_t -- type for number values (integer) + + @since version 1.0.0 */ using number_float_t = NumberFloatType; @@ -567,8 +603,11 @@ class basic_json @brief the JSON type enumeration This enumeration collects the different JSON types. It is internally used - to distinguish the stored values, and the functions is_null, is_object, - is_array, is_string, is_boolean, is_number, and is_discarded rely on it. + to distinguish the stored values, and the functions @ref is_null(), @ref + is_object(), @ref is_array(), @ref is_string(), @ref is_boolean(), @ref + is_number(), and @ref is_discarded() rely on it. + + @since version 1.0.0 */ enum class value_t : uint8_t { @@ -589,7 +628,7 @@ class basic_json static T* create(Args&& ... args) { AllocatorType alloc; - auto deleter = [&](T* object) + auto deleter = [&](T * object) { alloc.deallocate(object, 1); }; @@ -602,7 +641,13 @@ class basic_json // JSON value storage // //////////////////////// - /// a JSON value + /*! + @brief a JSON value + + The actual storage for a JSON value of the @ref basic_json class. + + @since version 1.0.0 + */ union json_value { /// object (stored with pointer to save storage) @@ -631,47 +676,46 @@ class basic_json { switch (t) { - case (value_t::null): - case (value_t::discarded): - { - break; - } - - case (value_t::object): + case value_t::object: { object = create(); break; } - case (value_t::array): + case value_t::array: { array = create(); break; } - case (value_t::string): + case value_t::string: { string = create(""); break; } - case (value_t::boolean): + case value_t::boolean: { boolean = boolean_t(false); break; } - case (value_t::number_integer): + case value_t::number_integer: { number_integer = number_integer_t(0); break; } - case (value_t::number_float): + case value_t::number_float: { number_float = number_float_t(0.0); break; } + + default: + { + break; + } } } @@ -705,6 +749,8 @@ class basic_json This enumeration lists the parser events that can trigger calling a callback function of type @ref parser_callback_t during parsing. + + @since version 1.0.0 */ enum class parse_event_t : uint8_t { @@ -746,17 +792,17 @@ class basic_json parse_event_t::array_end | the parser read `]` and finished processing a JSON array | depth of the parent of the JSON array | the parsed JSON array parse_event_t::value | the parser finished reading a JSON value | depth of the value | the parsed JSON value - Discarding a value (i.e., returning `false`) has different effects depending on the - context in which function was called: + Discarding a value (i.e., returning `false`) has different effects + depending on the context in which function was called: - Discarded values in structured types are skipped. That is, the parser will behave as if the discarded value was never read. - In case a value outside a structured type is skipped, it is replaced with `null`. This case happens if the top-level element is skipped. - @param[in] depth the depth of the recursion during parsing + @param[in] depth the depth of the recursion during parsing - @param[in] event an event of type parse_event_t indicating the context in + @param[in] event an event of type parse_event_t indicating the context in the callback function has been called @param[in,out] parsed the current intermediate parse result; note that @@ -768,15 +814,19 @@ class basic_json @sa @ref parse(std::istream&, parser_callback_t) or @ref parse(const string_t&, parser_callback_t) for examples + + @since version 1.0.0 */ - using parser_callback_t = std::function; + using parser_callback_t = std::function; ////////////////// // constructors // ////////////////// + /// @name constructors and destructors + /// @{ + /*! @brief create an empty value with a given type @@ -792,7 +842,7 @@ class basic_json object | `{}` array | `[]` - @param[in] value the type of the value to create + @param[in] value_type the type of the value to create @complexity Constant. @@ -801,9 +851,21 @@ class basic_json @liveexample{The following code shows the constructor for different @ref value_t values,basic_json__value_t} + + @sa @ref basic_json(std::nullptr_t) -- create a `null` value + @sa @ref basic_json(boolean_t value) -- create a boolean value + @sa @ref basic_json(const string_t&) -- create a string value + @sa @ref basic_json(const object_t&) -- create a object value + @sa @ref basic_json(const array_t&) -- create a array value + @sa @ref basic_json(const number_float_t) -- create a number + (floating-point) value + @sa @ref basic_json(const number_integer_t) -- create a number (integer) + value + + @since version 1.0.0 */ - basic_json(const value_t value) - : m_type(value), m_value(value) + basic_json(const value_t value_type) + : m_type(value_type), m_value(value_type) {} /*! @@ -821,7 +883,9 @@ class basic_json @liveexample{The following code shows the constructor for a `null` JSON value.,basic_json} - @sa basic_json(std::nullptr_t) + @sa @ref basic_json(std::nullptr_t) -- create a `null` value + + @since version 1.0.0 */ basic_json() noexcept = default; @@ -831,7 +895,7 @@ class basic_json Create a `null` JSON value. This is the explicitly version of the `null` value constructor as it takes a null pointer as parameter. It allows to create `null` values by explicitly assigning a @c nullptr to a JSON value. - The passed null pointer itself is not read - it is only used to choose the + The passed null pointer itself is not read -- it is only used to choose the right constructor. @complexity Constant. @@ -839,7 +903,10 @@ class basic_json @liveexample{The following code shows the constructor with null pointer parameter.,basic_json__nullptr_t} - @sa basic_json() + @sa @ref basic_json() -- default constructor (implicitly creating a `null` + value) + + @since version 1.0.0 */ basic_json(std::nullptr_t) noexcept : basic_json(value_t::null) @@ -850,19 +917,22 @@ class basic_json Create an object JSON value with a given content. - @param[in] value a value for the object + @param[in] val a value for the object - @complexity Linear in the size of the passed @a value. + @complexity Linear in the size of the passed @a val. @throw std::bad_alloc if allocation for object value fails @liveexample{The following code shows the constructor with an @ref object_t parameter.,basic_json__object_t} - @sa basic_json(const CompatibleObjectType&) + @sa @ref basic_json(const CompatibleObjectType&) -- create an object value + from a compatible STL container + + @since version 1.0.0 */ - basic_json(const object_t& value) - : m_type(value_t::object), m_value(value) + basic_json(const object_t& val) + : m_type(value_t::object), m_value(val) {} /*! @@ -875,28 +945,30 @@ class basic_json @tparam CompatibleObjectType an object type whose `key_type` and `value_type` is compatible to @ref object_t - @param[in] value a value for the object + @param[in] val a value for the object - @complexity Linear in the size of the passed @a value. + @complexity Linear in the size of the passed @a val. @throw std::bad_alloc if allocation for object value fails @liveexample{The following code shows the constructor with several compatible object type parameters.,basic_json__CompatibleObjectType} - @sa basic_json(const object_t&) + @sa @ref basic_json(const object_t&) -- create an object value + + @since version 1.0.0 */ template ::value and std::is_constructible::value, int>::type = 0> - basic_json(const CompatibleObjectType& value) + basic_json(const CompatibleObjectType& val) : m_type(value_t::object) { using std::begin; using std::end; - m_value.object = create(begin(value), end(value)); + m_value.object = create(begin(val), end(val)); } /*! @@ -904,19 +976,22 @@ class basic_json Create an array JSON value with a given content. - @param[in] value a value for the array + @param[in] val a value for the array - @complexity Linear in the size of the passed @a value. + @complexity Linear in the size of the passed @a val. @throw std::bad_alloc if allocation for array value fails @liveexample{The following code shows the constructor with an @ref array_t parameter.,basic_json__array_t} - @sa basic_json(const CompatibleArrayType&) + @sa @ref basic_json(const CompatibleArrayType&) -- create an array value + from a compatible STL containers + + @since version 1.0.0 */ - basic_json(const array_t& value) - : m_type(value_t::array), m_value(value) + basic_json(const array_t& val) + : m_type(value_t::array), m_value(val) {} /*! @@ -929,16 +1004,18 @@ class basic_json @tparam CompatibleArrayType an object type whose `value_type` is compatible to @ref array_t - @param[in] value a value for the array + @param[in] val a value for the array - @complexity Linear in the size of the passed @a value. + @complexity Linear in the size of the passed @a val. @throw std::bad_alloc if allocation for array value fails @liveexample{The following code shows the constructor with several compatible array type parameters.,basic_json__CompatibleArrayType} - @sa basic_json(const array_t&) + @sa @ref basic_json(const array_t&) -- create an array value + + @since version 1.0.0 */ template ::value and std::is_constructible::value, int>::type = 0> - basic_json(const CompatibleArrayType& value) + basic_json(const CompatibleArrayType& val) : m_type(value_t::array) { using std::begin; using std::end; - m_value.array = create(begin(value), end(value)); + m_value.array = create(begin(val), end(val)); } /*! @@ -963,20 +1040,24 @@ class basic_json Create an string JSON value with a given content. - @param[in] value a value for the string + @param[in] val a value for the string - @complexity Linear in the size of the passed @a value. + @complexity Linear in the size of the passed @a val. @throw std::bad_alloc if allocation for string value fails @liveexample{The following code shows the constructor with an @ref string_t parameter.,basic_json__string_t} - @sa basic_json(const typename string_t::value_type*) - @sa basic_json(const CompatibleStringType&) + @sa @ref basic_json(const typename string_t::value_type*) -- create a + string value from a character pointer + @sa @ref basic_json(const CompatibleStringType&) -- create a string value + from a compatible string container + + @since version 1.0.0 */ - basic_json(const string_t& value) - : m_type(value_t::string), m_value(value) + basic_json(const string_t& val) + : m_type(value_t::string), m_value(val) {} /*! @@ -984,20 +1065,23 @@ class basic_json Create a string JSON value with a given content. - @param[in] value a literal value for the string + @param[in] val a literal value for the string - @complexity Linear in the size of the passed @a value. + @complexity Linear in the size of the passed @a val. @throw std::bad_alloc if allocation for string value fails @liveexample{The following code shows the constructor with string literal parameter.,basic_json__string_t_value_type} - @sa basic_json(const string_t&) - @sa basic_json(const CompatibleStringType&) + @sa @ref basic_json(const string_t&) -- create a string value + @sa @ref basic_json(const CompatibleStringType&) -- create a string value + from a compatible string container + + @since version 1.0.0 */ - basic_json(const typename string_t::value_type* value) - : basic_json(string_t(value)) + basic_json(const typename string_t::value_type* val) + : basic_json(string_t(val)) {} /*! @@ -1005,26 +1089,30 @@ class basic_json Create a string JSON value with a given content. - @param[in] value a value for the string + @param[in] val a value for the string @tparam CompatibleStringType an string type which is compatible to @ref string_t - @complexity Linear in the size of the passed @a value. + @complexity Linear in the size of the passed @a val. @throw std::bad_alloc if allocation for string value fails @liveexample{The following code shows the construction of a string value from a compatible type.,basic_json__CompatibleStringType} - @sa basic_json(const string_t&) + @sa @ref basic_json(const string_t&) -- create a string value + @sa @ref basic_json(const typename string_t::value_type*) -- create a + string value from a character pointer + + @since version 1.0.0 */ template ::value, int>::type = 0> - basic_json(const CompatibleStringType& value) - : basic_json(string_t(value)) + basic_json(const CompatibleStringType& val) + : basic_json(string_t(val)) {} /*! @@ -1032,26 +1120,28 @@ class basic_json Creates a JSON boolean type from a given value. - @param[in] value a boolean value to store + @param[in] val a boolean value to store @complexity Constant. @liveexample{The example below demonstrates boolean values.,basic_json__boolean_t} + + @since version 1.0.0 */ - basic_json(boolean_t value) - : m_type(value_t::boolean), m_value(value) + basic_json(boolean_t val) + : m_type(value_t::boolean), m_value(val) {} /*! @brief create an integer number (explicit) - Create an interger number JSON value with a given content. + Create an integer number JSON value with a given content. @tparam T helper type to compare number_integer_t and int (not visible in) the interface. - @param[in] value an integer to create a JSON number from + @param[in] val an integer to create a JSON number from @note This constructor would have the same signature as @ref basic_json(const int value), so we need to switch this one off in case @@ -1062,15 +1152,19 @@ class basic_json @liveexample{The example below shows the construction of a JSON integer number value.,basic_json__number_integer_t} - @sa basic_json(const int) + @sa @ref basic_json(const int) -- create a number value (integer) + @sa @ref basic_json(const CompatibleNumberIntegerType) -- create a number + value (integer) from a compatible number type + + @since version 1.0.0 */ template::value) and std::is_same::value , int>::type = 0> - basic_json(const number_integer_t value) - : m_type(value_t::number_integer), m_value(value) + basic_json(const number_integer_t val) + : m_type(value_t::number_integer), m_value(val) {} /*! @@ -1078,7 +1172,7 @@ class basic_json Create an integer number JSON value with a given content. - @param[in] value an integer to create a JSON number from + @param[in] val an integer to create a JSON number from @note This constructor allows to pass enums directly to a constructor. As C++ has no way of specifying the type of an anonymous enum explicitly, we @@ -1091,11 +1185,16 @@ class basic_json @liveexample{The example below shows the construction of a JSON integer number value from an anonymous enum.,basic_json__const_int} - @sa basic_json(const number_integer_t) + @sa @ref basic_json(const number_integer_t) -- create a number value + (integer) + @sa @ref basic_json(const CompatibleNumberIntegerType) -- create a number + value (integer) from a compatible number type + + @since version 1.0.0 */ - basic_json(const int value) + basic_json(const int val) : m_type(value_t::number_integer), - m_value(static_cast(value)) + m_value(static_cast(val)) {} /*! @@ -1109,7 +1208,7 @@ class basic_json @tparam CompatibleNumberIntegerType an integer type which is compatible to @ref number_integer_t. - @param[in] value an integer to create a JSON number from + @param[in] val an integer to create a JSON number from @complexity Constant. @@ -1117,16 +1216,20 @@ class basic_json integer number values from compatible types.,basic_json__CompatibleIntegerNumberType} - @sa basic_json(const number_integer_t) + @sa @ref basic_json(const number_integer_t) -- create a number value + (integer) + @sa @ref basic_json(const int) -- create a number value (integer) + + @since version 1.0.0 */ template::value and std::numeric_limits::is_integer, CompatibleNumberIntegerType>::type = 0> - basic_json(const CompatibleNumberIntegerType value) noexcept + basic_json(const CompatibleNumberIntegerType val) noexcept : m_type(value_t::number_integer), - m_value(static_cast(value)) + m_value(static_cast(val)) {} /*! @@ -1134,25 +1237,30 @@ class basic_json Create a floating-point number JSON value with a given content. - @param[in] value a floating-point value to create a JSON number from + @param[in] val a floating-point value to create a JSON number from @note RFC 7159 , section 6 disallows NaN values: > Numeric values that cannot be represented in the grammar below (such > as Infinity and NaN) are not permitted. - In case the parameter @a value is not a number, a JSON null value is + In case the parameter @a val is not a number, a JSON null value is created instead. @complexity Constant. @liveexample{The following example creates several floating-point values.,basic_json__number_float_t} + + @sa @ref basic_json(const CompatibleNumberFloatType) -- create a number + value (floating-point) from a compatible number type + + @since version 1.0.0 */ - basic_json(const number_float_t value) - : m_type(value_t::number_float), m_value(value) + basic_json(const number_float_t val) + : m_type(value_t::number_float), m_value(val) { // replace infinity and NAN by null - if (not std::isfinite(value)) + if (not std::isfinite(val)) { m_type = value_t::null; m_value = json_value(); @@ -1169,13 +1277,13 @@ class basic_json @tparam CompatibleNumberFloatType a floating-point type which is compatible to @ref number_float_t. - @param[in] value a floating-point to create a JSON number from + @param[in] val a floating-point to create a JSON number from @note RFC 7159 , section 6 disallows NaN values: > Numeric values that cannot be represented in the grammar below (such > as Infinity and NaN) are not permitted. - In case the parameter @a value is not a number, a JSON null value is + In case the parameter @a val is not a number, a JSON null value is created instead. @complexity Constant. @@ -1184,15 +1292,18 @@ class basic_json floating-point number values from compatible types.,basic_json__CompatibleNumberFloatType} - @sa basic_json(const number_float_t) + @sa @ref basic_json(const number_float_t) -- create a number value + (floating-point) + + @since version 1.0.0 */ template::value and std::is_floating_point::value>::type > - basic_json(const CompatibleNumberFloatType value) noexcept - : basic_json(number_float_t(value)) + basic_json(const CompatibleNumberFloatType val) noexcept + : basic_json(number_float_t(val)) {} /*! @@ -1210,7 +1321,7 @@ class basic_json 3. In all other cases, an array is created. The rules aim to create the best fit between a C++ initializer list and - JSON values. The ratioinale is as follows: + JSON values. The rationale is as follows: 1. The empty initializer list is written as `{}` which is exactly an empty JSON object. @@ -1249,35 +1360,38 @@ class basic_json @throw std::domain_error if @a type_deduction is `false`, @a manual_type is `value_t::object`, but @a init contains an element which is not a pair - whose first element is a string + whose first element is a string; example: `"cannot create object from + initializer list"` @complexity Linear in the size of the initializer list @a init. @liveexample{The example below shows how JSON values are created from initializer lists,basic_json__list_init_t} - @sa basic_json array(std::initializer_list) - create a JSON - array value from an initializer list - @sa basic_json object(std::initializer_list) - create a JSON - object value from an initializer list + @sa @ref array(std::initializer_list) -- create a JSON array + value from an initializer list + @sa @ref object(std::initializer_list) -- create a JSON object + value from an initializer list + + @since version 1.0.0 */ basic_json(std::initializer_list init, bool type_deduction = true, value_t manual_type = value_t::array) { // the initializer list could describe an object - bool is_object = true; + bool is_an_object = true; - // check if each element is an array with two elements whose first element - // is a string + // check if each element is an array with two elements whose first + // element is a string for (const auto& element : init) { - if (element.m_type != value_t::array or element.size() != 2 - or element[0].m_type != value_t::string) + if (not element.is_array() or element.size() != 2 + or not element[0].is_string()) { // we found an element that makes it impossible to use the // initializer list as object - is_object = false; + is_an_object = false; break; } } @@ -1288,22 +1402,24 @@ class basic_json // if array is wanted, do not create an object though possible if (manual_type == value_t::array) { - is_object = false; + is_an_object = false; } // if object is wanted but impossible, throw an exception - if (manual_type == value_t::object and not is_object) + if (manual_type == value_t::object and not is_an_object) { throw std::domain_error("cannot create object from initializer list"); } } - if (is_object) + if (is_an_object) { // the initializer list is a list of pairs -> create object m_type = value_t::object; m_value = value_t::object; + assert(m_value.object != nullptr); + for (auto& element : init) { m_value.object->emplace(std::move(*(element[0].m_value.string)), std::move(element[1])); @@ -1329,9 +1445,9 @@ class basic_json basic_json(std::initializer_list, bool, value_t)). These cases are: 1. creating an array whose elements are all pairs whose first element is a - string - in this case, the initializer list constructor would create an + string -- in this case, the initializer list constructor would create an object, taking the first elements as keys - 2. creating an empty array - passing the empty initializer list to the + 2. creating an empty array -- passing the empty initializer list to the initializer list constructor yields an empty object @param[in] init initializer list with JSON values to create an array from @@ -1344,10 +1460,12 @@ class basic_json @liveexample{The following code shows an example for the @ref array function.,array} - @sa basic_json(std::initializer_list, bool, value_t) - create a - JSON value from an initializer list - @sa basic_json object(std::initializer_list) - create a JSON - object value from an initializer list + @sa @ref basic_json(std::initializer_list, bool, value_t) -- + create a JSON value from an initializer list + @sa @ref object(std::initializer_list) -- create a JSON object + value from an initializer list + + @since version 1.0.0 */ static basic_json array(std::initializer_list init = std::initializer_list()) @@ -1359,33 +1477,35 @@ class basic_json @brief explicitly create an object from an initializer list Creates a JSON object value from a given initializer list. The initializer - lists elements must be pairs, and their first elments must be strings. If + lists elements must be pairs, and their first elements must be strings. If the initializer list is empty, the empty object `{}` is created. @note This function is only added for symmetry reasons. In contrast to the - related function @ref basic_json array(std::initializer_list), - there are no cases which can only be expressed by this function. That is, - any initializer list @a init can also be passed to the initializer list - constructor @ref basic_json(std::initializer_list, bool, - value_t). + related function @ref array(std::initializer_list), there are + no cases which can only be expressed by this function. That is, any + initializer list @a init can also be passed to the initializer list + constructor + @ref basic_json(std::initializer_list, bool, value_t). @param[in] init initializer list to create an object from (optional) @return JSON object value @throw std::domain_error if @a init is not a pair whose first elements are - strings; thrown by @ref basic_json(std::initializer_list, bool, - value_t) + strings; thrown by + @ref basic_json(std::initializer_list, bool, value_t) @complexity Linear in the size of @a init. @liveexample{The following code shows an example for the @ref object function.,object} - @sa basic_json(std::initializer_list, bool, value_t) - create a - JSON value from an initializer list - @sa basic_json array(std::initializer_list) - create a JSON - array value from an initializer list + @sa @ref basic_json(std::initializer_list, bool, value_t) -- + create a JSON value from an initializer list + @sa @ref array(std::initializer_list) -- create a JSON array + value from an initializer list + + @since version 1.0.0 */ static basic_json object(std::initializer_list init = std::initializer_list()) @@ -1396,23 +1516,25 @@ class basic_json /*! @brief construct an array with count copies of given value - Constructs a JSON array value by creating @a count copies of a passed - value. In case @a count is `0`, an empty array is created. As postcondition, - `std::distance(begin(),end()) == count` holds. + Constructs a JSON array value by creating @a cnt copies of a passed + value. In case @a cnt is `0`, an empty array is created. As postcondition, + `std::distance(begin(),end()) == cnt` holds. - @param[in] count the number of JSON copies of @a value to create - @param[in] value the JSON value to copy + @param[in] cnt the number of JSON copies of @a val to create + @param[in] val the JSON value to copy - @complexity Linear in @a count. + @complexity Linear in @a cnt. @liveexample{The following code shows examples for the @ref basic_json(size_type\, const basic_json&) constructor.,basic_json__size_type_basic_json} + + @since version 1.0.0 */ - basic_json(size_type count, const basic_json& value) + basic_json(size_type cnt, const basic_json& val) : m_type(value_t::array) { - m_value.array = create(count, value); + m_value.array = create(cnt, val); } /*! @@ -1434,16 +1556,20 @@ class basic_json @param[in] last end of the range to copy from (excluded) @throw std::domain_error if iterators are not compatible; that is, do not - belong to the same JSON value + belong to the same JSON value; example: `"iterators are not compatible"` @throw std::out_of_range if iterators are for a primitive type (number, - boolean, or string) where an out of range error can be detected easily + boolean, or string) where an out of range error can be detected easily; + example: `"iterators out of range"` @throw std::bad_alloc if allocation for object, array, or string fails - @throw std::domain_error if called with a null value + @throw std::domain_error if called with a null value; example: `"cannot use + construct with iterators from null"` @complexity Linear in distance between @a first and @a last. @liveexample{The example below shows several ways to create JSON values by specifying a subrange with iterators.,basic_json__InputIt_InputIt} + + @since version 1.0.0 */ template m_value.number_integer; break; } case value_t::number_float: { + assert(first.m_object != nullptr); m_value.number_float = first.m_object->m_value.number_float; break; } case value_t::boolean: { + assert(first.m_object != nullptr); m_value.boolean = first.m_object->m_value.boolean; break; } case value_t::string: { + assert(first.m_object != nullptr); m_value = *first.m_object->m_value.string; break; } @@ -1520,6 +1650,7 @@ class basic_json default: { + assert(first.m_object != nullptr); throw std::domain_error("cannot use construct with iterators from " + first.m_object->type_name()); } } @@ -1546,53 +1677,57 @@ class basic_json @liveexample{The following code shows an example for the copy constructor.,basic_json__basic_json} + + @since version 1.0.0 */ basic_json(const basic_json& other) : m_type(other.m_type) { switch (m_type) { - case (value_t::null): - case (value_t::discarded): - { - break; - } - - case (value_t::object): + case value_t::object: { + assert(other.m_value.object != nullptr); m_value = *other.m_value.object; break; } - case (value_t::array): + case value_t::array: { + assert(other.m_value.array != nullptr); m_value = *other.m_value.array; break; } - case (value_t::string): + case value_t::string: { + assert(other.m_value.string != nullptr); m_value = *other.m_value.string; break; } - case (value_t::boolean): + case value_t::boolean: { m_value = other.m_value.boolean; break; } - case (value_t::number_integer): + case value_t::number_integer: { m_value = other.m_value.number_integer; break; } - case (value_t::number_float): + case value_t::number_float: { m_value = other.m_value.number_float; break; } + + default: + { + break; + } } } @@ -1611,6 +1746,8 @@ class basic_json @liveexample{The code below shows the move constructor explicitly called via std::move.,basic_json__moveconstructor} + + @since version 1.0.0 */ basic_json(basic_json&& other) noexcept : m_type(std::move(other.m_type)), @@ -1639,6 +1776,8 @@ class basic_json creates a copy of value `a` which is then swapped with `b`. Finally\, the copy of `a` (which is the null value after the swap) is destroyed.,basic_json__copyassignment} + + @since version 1.0.0 */ reference& operator=(basic_json other) noexcept ( std::is_nothrow_move_constructible::value and @@ -1663,12 +1802,14 @@ class basic_json @requirement This function satisfies the Container requirements: - The complexity is linear. - All stored elements are destroyed and all memory is freed. + + @since version 1.0.0 */ ~basic_json() { switch (m_type) { - case (value_t::object): + case value_t::object: { AllocatorType alloc; alloc.destroy(m_value.object); @@ -1676,7 +1817,7 @@ class basic_json break; } - case (value_t::array): + case value_t::array: { AllocatorType alloc; alloc.destroy(m_value.array); @@ -1684,7 +1825,7 @@ class basic_json break; } - case (value_t::string): + case value_t::string: { AllocatorType alloc; alloc.destroy(m_value.string); @@ -1700,6 +1841,7 @@ class basic_json } } + /// @} public: /////////////////////// @@ -1712,7 +1854,7 @@ class basic_json /*! @brief serialization - Serialization function for JSON values. The function tries to mimick + Serialization function for JSON values. The function tries to mimic Python's @p json.dumps() function, and currently supports its @p indent parameter. @@ -1726,9 +1868,11 @@ class basic_json @complexity Linear. @liveexample{The following example shows the effect of different @a indent - parameters to the result of the serializaion.,dump} + parameters to the result of the serialization.,dump} @see https://docs.python.org/2/library/json.html#json.dump + + @since version 1.0.0 */ string_t dump(const int indent = -1) const { @@ -1758,6 +1902,8 @@ class basic_json @liveexample{The following code exemplifies @ref type() for all JSON types.,type} + + @since version 1.0.0 */ value_t type() const noexcept { @@ -1777,6 +1923,8 @@ class basic_json @liveexample{The following code exemplifies @ref is_primitive for all JSON types.,is_primitive} + + @since version 1.0.0 */ bool is_primitive() const noexcept { @@ -1795,6 +1943,8 @@ class basic_json @liveexample{The following code exemplifies @ref is_structured for all JSON types.,is_structured} + + @since version 1.0.0 */ bool is_structured() const noexcept { @@ -1812,6 +1962,8 @@ class basic_json @liveexample{The following code exemplifies @ref is_null for all JSON types.,is_null} + + @since version 1.0.0 */ bool is_null() const noexcept { @@ -1829,6 +1981,8 @@ class basic_json @liveexample{The following code exemplifies @ref is_boolean for all JSON types.,is_boolean} + + @since version 1.0.0 */ bool is_boolean() const noexcept { @@ -1841,12 +1995,18 @@ class basic_json This function returns true iff the JSON value is a number. This includes both integer and floating-point values. - @return `true` if type is number, `false` otherwise. + @return `true` if type is number (regardless whether integer or + floating-type), `false` otherwise. @complexity Constant. @liveexample{The following code exemplifies @ref is_number for all JSON types.,is_number} + + @sa @ref is_number_integer() -- check if value is an integer number + @sa @ref is_number_float() -- check if value is a floating-point number + + @since version 1.0.0 */ bool is_number() const noexcept { @@ -1865,6 +2025,11 @@ class basic_json @liveexample{The following code exemplifies @ref is_number_integer for all JSON types.,is_number_integer} + + @sa @ref is_number() -- check if value is a number + @sa @ref is_number_float() -- check if value is a floating-point number + + @since version 1.0.0 */ bool is_number_integer() const noexcept { @@ -1883,6 +2048,11 @@ class basic_json @liveexample{The following code exemplifies @ref is_number_float for all JSON types.,is_number_float} + + @sa @ref is_number() -- check if value is number + @sa @ref is_number_integer() -- check if value is an integer number + + @since version 1.0.0 */ bool is_number_float() const noexcept { @@ -1900,6 +2070,8 @@ class basic_json @liveexample{The following code exemplifies @ref is_object for all JSON types.,is_object} + + @since version 1.0.0 */ bool is_object() const noexcept { @@ -1917,6 +2089,8 @@ class basic_json @liveexample{The following code exemplifies @ref is_array for all JSON types.,is_array} + + @since version 1.0.0 */ bool is_array() const noexcept { @@ -1934,6 +2108,8 @@ class basic_json @liveexample{The following code exemplifies @ref is_string for all JSON types.,is_string} + + @since version 1.0.0 */ bool is_string() const noexcept { @@ -1956,6 +2132,8 @@ class basic_json @liveexample{The following code exemplifies @ref is_discarded for all JSON types.,is_discarded} + + @since version 1.0.0 */ bool is_discarded() const noexcept { @@ -1974,6 +2152,8 @@ class basic_json @liveexample{The following code exemplifies the value_t operator for all JSON types.,operator__value_t} + + @since version 1.0.0 */ operator value_t() const noexcept { @@ -1995,32 +2175,28 @@ class basic_json , int>::type = 0> T get_impl(T*) const { - switch (m_type) + if (is_object()) { - case (value_t::object): - { - return T(m_value.object->begin(), m_value.object->end()); - } - default: - { - throw std::domain_error("type must be object, but is " + type_name()); - } + assert(m_value.object != nullptr); + return T(m_value.object->begin(), m_value.object->end()); + } + else + { + throw std::domain_error("type must be object, but is " + type_name()); } } /// get an object (explicit) object_t get_impl(object_t*) const { - switch (m_type) + if (is_object()) { - case (value_t::object): - { - return *(m_value.object); - } - default: - { - throw std::domain_error("type must be object, but is " + type_name()); - } + assert(m_value.object != nullptr); + return *(m_value.object); + } + else + { + throw std::domain_error("type must be object, but is " + type_name()); } } @@ -2035,22 +2211,20 @@ class basic_json , int>::type = 0> T get_impl(T*) const { - switch (m_type) + if (is_array()) { - case (value_t::array): + T to_vector; + assert(m_value.array != nullptr); + std::transform(m_value.array->begin(), m_value.array->end(), + std::inserter(to_vector, to_vector.end()), [](basic_json i) { - T to_vector; - std::transform(m_value.array->begin(), m_value.array->end(), - std::inserter(to_vector, to_vector.end()), [](basic_json i) - { - return i.get(); - }); - return to_vector; - } - default: - { - throw std::domain_error("type must be array, but is " + type_name()); - } + return i.get(); + }); + return to_vector; + } + else + { + throw std::domain_error("type must be array, but is " + type_name()); } } @@ -2062,23 +2236,21 @@ class basic_json , int>::type = 0> std::vector get_impl(std::vector*) const { - switch (m_type) + if (is_array()) { - case (value_t::array): + std::vector to_vector; + assert(m_value.array != nullptr); + to_vector.reserve(m_value.array->size()); + std::transform(m_value.array->begin(), m_value.array->end(), + std::inserter(to_vector, to_vector.end()), [](basic_json i) { - std::vector to_vector; - to_vector.reserve(m_value.array->size()); - std::transform(m_value.array->begin(), m_value.array->end(), - std::inserter(to_vector, to_vector.end()), [](basic_json i) - { - return i.get(); - }); - return to_vector; - } - default: - { - throw std::domain_error("type must be array, but is " + type_name()); - } + return i.get(); + }); + return to_vector; + } + else + { + throw std::domain_error("type must be array, but is " + type_name()); } } @@ -2090,32 +2262,28 @@ class basic_json , int>::type = 0> T get_impl(T*) const { - switch (m_type) + if (is_array()) { - case (value_t::array): - { - return T(m_value.array->begin(), m_value.array->end()); - } - default: - { - throw std::domain_error("type must be array, but is " + type_name()); - } + assert(m_value.array != nullptr); + return T(m_value.array->begin(), m_value.array->end()); + } + else + { + throw std::domain_error("type must be array, but is " + type_name()); } } /// get an array (explicit) array_t get_impl(array_t*) const { - switch (m_type) + if (is_array()) { - case (value_t::array): - { - return *(m_value.array); - } - default: - { - throw std::domain_error("type must be array, but is " + type_name()); - } + assert(m_value.array != nullptr); + return *(m_value.array); + } + else + { + throw std::domain_error("type must be array, but is " + type_name()); } } @@ -2126,16 +2294,14 @@ class basic_json , int>::type = 0> T get_impl(T*) const { - switch (m_type) + if (is_string()) { - case (value_t::string): - { - return *m_value.string; - } - default: - { - throw std::domain_error("type must be string, but is " + type_name()); - } + assert(m_value.string != nullptr); + return *m_value.string; + } + else + { + throw std::domain_error("type must be string, but is " + type_name()); } } @@ -2148,14 +2314,16 @@ class basic_json { switch (m_type) { - case (value_t::number_integer): + case value_t::number_integer: { return static_cast(m_value.number_integer); } - case (value_t::number_float): + + case value_t::number_float: { return static_cast(m_value.number_float); } + default: { throw std::domain_error("type must be number, but is " + type_name()); @@ -2166,16 +2334,13 @@ class basic_json /// get a boolean (explicit) boolean_t get_impl(boolean_t*) const { - switch (m_type) + if (is_boolean()) { - case (value_t::boolean): - { - return m_value.boolean; - } - default: - { - throw std::domain_error("type must be boolean, but is " + type_name()); - } + return m_value.boolean; + } + else + { + throw std::domain_error("type must be boolean, but is " + type_name()); } } @@ -2268,15 +2433,15 @@ class basic_json @return copy of the JSON value, converted to type @a ValueType @throw std::domain_error in case passed type @a ValueType is incompatible - to JSON + to JSON; example: `"type must be object, but is null"` @complexity Linear in the size of the JSON value. - @liveexample{The example below shows serveral conversions from JSON values + @liveexample{The example below shows several conversions from JSON values to other types. There a few things to note: (1) Floating-point numbers can be converted to integers\, (2) A JSON array can be converted to a standard `std::vector`\, (3) A JSON object can be converted to C++ - assiciative containers such as `std::unordered_map`.,get__ValueType_const} @internal @@ -2286,6 +2451,8 @@ class basic_json @sa @ref operator ValueType() const for implicit conversion @sa @ref get() for pointer-member access + + @since version 1.0.0 */ template::value - and std::is_const::value + and std::is_const::type>::value , int>::type = 0> const PointerType get_ptr() const noexcept { @@ -2396,12 +2566,14 @@ class basic_json /*! @brief get a value (implicit) - Implict type conversion between the JSON value and a compatible value. The + Implicit type conversion between the JSON value and a compatible value. The call is realized by calling @ref get() const. @tparam ValueType non-pointer type compatible to the JSON value, for instance `int` for JSON integer numbers, `bool` for JSON booleans, or - `std::vector` types for JSON arrays + `std::vector` types for JSON arrays. The character type of @ref string_t + as well as an initializer list of this type is excluded to avoid + ambiguities as these types implicitly convert to `std::string`. @return copy of the JSON value, converted to type @a ValueType @@ -2410,16 +2582,20 @@ class basic_json @complexity Linear in the size of the JSON value. - @liveexample{The example below shows serveral conversions from JSON values + @liveexample{The example below shows several conversions from JSON values to other types. There a few things to note: (1) Floating-point numbers can be converted to integers\, (2) A JSON array can be converted to a standard `std::vector`\, (3) A JSON object can be converted to C++ - assiciative containers such as `std::unordered_map`.,operator__ValueType} + + @since version 1.0.0 */ template::value + and not std::is_same::value + and not std::is_same>::value , int>::type = 0> operator ValueType() const { @@ -2447,24 +2623,38 @@ class basic_json @return reference to the element at index @a idx - @throw std::domain_error if JSON is not an array + @throw std::domain_error if the JSON value is not an array; example: + `"cannot use at() with string"` @throw std::out_of_range if the index @a idx is out of range of the array; - that is, `idx >= size()` + that is, `idx >= size()`; example: `"array index 7 is out of range"` @complexity Constant. @liveexample{The example below shows how array elements can be read and written using at.,at__size_type} + + @since version 1.0.0 */ reference at(size_type idx) { // at only works for arrays - if (m_type != value_t::array) + if (is_array()) + { + try + { + assert(m_value.array != nullptr); + return m_value.array->at(idx); + } + catch (std::out_of_range&) + { + // create better exception explanation + throw std::out_of_range("array index " + std::to_string(idx) + " is out of range"); + } + } + else { throw std::domain_error("cannot use at() with " + type_name()); } - - return m_value.array->at(idx); } /*! @@ -2477,24 +2667,38 @@ class basic_json @return const reference to the element at index @a idx - @throw std::domain_error if JSON is not an array + @throw std::domain_error if the JSON value is not an array; example: + `"cannot use at() with string"` @throw std::out_of_range if the index @a idx is out of range of the array; - that is, `idx >= size()` + that is, `idx >= size()`; example: `"array index 7 is out of range"` @complexity Constant. @liveexample{The example below shows how array elements can be read using at.,at__size_type_const} + + @since version 1.0.0 */ const_reference at(size_type idx) const { // at only works for arrays - if (m_type != value_t::array) + if (is_array()) + { + try + { + assert(m_value.array != nullptr); + return m_value.array->at(idx); + } + catch (std::out_of_range&) + { + // create better exception explanation + throw std::out_of_range("array index " + std::to_string(idx) + " is out of range"); + } + } + else { throw std::domain_error("cannot use at() with " + type_name()); } - - return m_value.array->at(idx); } /*! @@ -2507,24 +2711,42 @@ class basic_json @return reference to the element at key @a key - @throw std::domain_error if JSON is not an object + @throw std::domain_error if the JSON value is not an object; example: + `"cannot use at() with boolean"` @throw std::out_of_range if the key @a key is is not stored in the object; - that is, `find(key) == end()` + that is, `find(key) == end()`; example: `"key "the fast" not found"` @complexity Logarithmic in the size of the container. @liveexample{The example below shows how object elements can be read and written using at.,at__object_t_key_type} + + @sa @ref operator[](const typename object_t::key_type&) for unchecked + access by reference + @sa @ref value() for access by value with a default value + + @since version 1.0.0 */ reference at(const typename object_t::key_type& key) { // at only works for objects - if (m_type != value_t::object) + if (is_object()) + { + try + { + assert(m_value.object != nullptr); + return m_value.object->at(key); + } + catch (std::out_of_range&) + { + // create better exception explanation + throw std::out_of_range("key '" + key + "' not found"); + } + } + else { throw std::domain_error("cannot use at() with " + type_name()); } - - return m_value.object->at(key); } /*! @@ -2537,24 +2759,42 @@ class basic_json @return const reference to the element at key @a key - @throw std::domain_error if JSON is not an object + @throw std::domain_error if the JSON value is not an object; example: + `"cannot use at() with boolean"` @throw std::out_of_range if the key @a key is is not stored in the object; - that is, `find(key) == end()` + that is, `find(key) == end()`; example: `"key "the fast" not found"` @complexity Logarithmic in the size of the container. @liveexample{The example below shows how object elements can be read using at.,at__object_t_key_type_const} + + @sa @ref operator[](const typename object_t::key_type&) for unchecked + access by reference + @sa @ref value() for access by value with a default value + + @since version 1.0.0 */ const_reference at(const typename object_t::key_type& key) const { // at only works for objects - if (m_type != value_t::object) + if (is_object()) + { + try + { + assert(m_value.object != nullptr); + return m_value.object->at(key); + } + catch (std::out_of_range&) + { + // create better exception explanation + throw std::out_of_range("key '" + key + "' not found"); + } + } + else { throw std::domain_error("cannot use at() with " + type_name()); } - - return m_value.object->at(key); } /*! @@ -2570,7 +2810,8 @@ class basic_json @return reference to the element at index @a idx - @throw std::domain_error if JSON is not an array or null + @throw std::domain_error if JSON is not an array or null; example: `"cannot + use operator[] with null"` @complexity Constant if @a idx is in the range of the array. Otherwise linear in `idx - size()`. @@ -2578,28 +2819,33 @@ class basic_json @liveexample{The example below shows how array elements can be read and written using [] operator. Note the addition of `null` values.,operatorarray__size_type} + + @since version 1.0.0 */ reference operator[](size_type idx) { // implicitly convert null to object - if (m_type == value_t::null) + if (is_null()) { m_type = value_t::array; m_value.array = create(); } // [] only works for arrays - if (m_type != value_t::array) + if (is_array()) + { + assert(m_value.array != nullptr); + for (size_t i = m_value.array->size(); i <= idx; ++i) + { + m_value.array->push_back(basic_json()); + } + + return m_value.array->operator[](idx); + } + else { throw std::domain_error("cannot use operator[] with " + type_name()); } - - for (size_t i = m_value.array->size(); i <= idx; ++i) - { - m_value.array->push_back(basic_json()); - } - - return m_value.array->operator[](idx); } /*! @@ -2611,22 +2857,28 @@ class basic_json @return const reference to the element at index @a idx - @throw std::domain_error if JSON is not an array + @throw std::domain_error if JSON is not an array; example: `"cannot use + operator[] with null"` @complexity Constant. @liveexample{The example below shows how array elements can be read using the [] operator.,operatorarray__size_type_const} + + @since version 1.0.0 */ const_reference operator[](size_type idx) const { // at only works for arrays - if (m_type != value_t::array) + if (is_array()) + { + assert(m_value.array != nullptr); + return m_value.array->operator[](idx); + } + else { throw std::domain_error("cannot use operator[] with " + type_name()); } - - return m_value.array->operator[](idx); } /*! @@ -2642,56 +2894,81 @@ class basic_json @return reference to the element at key @a key - @throw std::domain_error if JSON is not an object or null + @throw std::domain_error if JSON is not an object or null; example: + `"cannot use operator[] with null"` @complexity Logarithmic in the size of the container. @liveexample{The example below shows how object elements can be read and written using the [] operator.,operatorarray__key_type} + + @sa @ref at(const typename object_t::key_type&) for access by reference + with range checking + @sa @ref value() for access by value with a default value + + @since version 1.0.0 */ reference operator[](const typename object_t::key_type& key) { // implicitly convert null to object - if (m_type == value_t::null) + if (is_null()) { m_type = value_t::object; m_value.object = create(); } // [] only works for objects - if (m_type != value_t::object) + if (is_object()) + { + assert(m_value.object != nullptr); + return m_value.object->operator[](key); + } + else { throw std::domain_error("cannot use operator[] with " + type_name()); } - - return m_value.object->operator[](key); } /*! - @brief access specified object element + @brief read-only access specified object element - Returns a reference to the element at with specified key @a key. + Returns a const reference to the element at with specified key @a key. No + bounds checking is performed. + + @warning If the element with key @a key does not exist, the behavior is + undefined. @param[in] key key of the element to access - @return reference to the element at key @a key + @return const reference to the element at key @a key - @throw std::domain_error if JSON is not an object or null + @throw std::domain_error if JSON is not an object; example: `"cannot use + operator[] with null"` @complexity Logarithmic in the size of the container. @liveexample{The example below shows how object elements can be read using the [] operator.,operatorarray__key_type_const} + + @sa @ref at(const typename object_t::key_type&) for access by reference + with range checking + @sa @ref value() for access by value with a default value + + @since version 1.0.0 */ const_reference operator[](const typename object_t::key_type& key) const { - // at only works for objects - if (m_type != value_t::object) + // [] only works for objects + if (is_object()) + { + assert(m_value.object != nullptr); + assert(m_value.object->find(key) != m_value.object->end()); + return m_value.object->find(key)->second; + } + else { throw std::domain_error("cannot use operator[] with " + type_name()); } - - return m_value.object->operator[](key); } /*! @@ -2709,60 +2986,168 @@ class basic_json @return reference to the element at key @a key - @throw std::domain_error if JSON is not an object or null + @throw std::domain_error if JSON is not an object or null; example: + `"cannot use operator[] with null"` @complexity Logarithmic in the size of the container. @liveexample{The example below shows how object elements can be read and written using the [] operator.,operatorarray__key_type} + + @sa @ref at(const typename object_t::key_type&) for access by reference + with range checking + @sa @ref value() for access by value with a default value + + @since version 1.0.0 */ template reference operator[](const T (&key)[n]) { // implicitly convert null to object - if (m_type == value_t::null) + if (is_null()) { m_type = value_t::object; m_value = value_t::object; } // at only works for objects - if (m_type != value_t::object) + if (is_object()) + { + assert(m_value.object != nullptr); + return m_value.object->operator[](key); + } + else { throw std::domain_error("cannot use operator[] with " + type_name()); } - - return m_value.object->operator[](key); } /*! - @brief access specified object element + @brief read-only access specified object element - Returns a reference to the element at with specified key @a key. + Returns a const reference to the element at with specified key @a key. No + bounds checking is performed. + + @warning If the element with key @a key does not exist, the behavior is + undefined. @note This function is required for compatibility reasons with Clang. @param[in] key key of the element to access - @return reference to the element at key @a key + @return const reference to the element at key @a key - @throw std::domain_error if JSON is not an object or null + @throw std::domain_error if JSON is not an object; example: `"cannot use + operator[] with null"` @complexity Logarithmic in the size of the container. @liveexample{The example below shows how object elements can be read using the [] operator.,operatorarray__key_type_const} + + @sa @ref at(const typename object_t::key_type&) for access by reference + with range checking + @sa @ref value() for access by value with a default value + + @since version 1.0.0 */ template const_reference operator[](const T (&key)[n]) const { // at only works for objects - if (m_type != value_t::object) + if (is_object()) + { + assert(m_value.object != nullptr); + assert(m_value.object->find(key) != m_value.object->end()); + return m_value.object->find(key)->second; + } + else { throw std::domain_error("cannot use operator[] with " + type_name()); } + } - return m_value.object->operator[](key); + /*! + @brief access specified object element with default value + + Returns either a copy of an object's element at the specified key @a key or + a given default value if no element with key @a key exists. + + The function is basically equivalent to executing + @code {.cpp} + try { + return at(key); + } catch(std::out_of_range) { + return default_value; + } + @endcode + + @note Unlike @ref at(const typename object_t::key_type&), this function + does not throw if the given key @a key was not found. + + @note Unlike @ref operator[](const typename object_t::key_type& key), this + function does not implicitly add an element to the position defined by @a + key. This function is furthermore also applicable to const objects. + + @param[in] key key of the element to access + @param[in] default_value the value to return if @a key is not found + + @tparam ValueType type compatible to JSON values, for instance `int` for + JSON integer numbers, `bool` for JSON booleans, or `std::vector` types for + JSON arrays. Note the type of the expected value at @a key and the default + value @a default_value must be compatible. + + @return copy of the element at key @a key or @a default_value if @a key + is not found + + @throw std::domain_error if JSON is not an object; example: `"cannot use + value() with null"` + + @complexity Logarithmic in the size of the container. + + @liveexample{The example below shows how object elements can be queried + with a default value.,basic_json__value} + + @sa @ref at(const typename object_t::key_type&) for access by reference + with range checking + @sa @ref operator[](const typename object_t::key_type&) for unchecked + access by reference + + @since version 1.0.0 + */ + template ::value + , int>::type = 0> + ValueType value(const typename object_t::key_type& key, ValueType default_value) const + { + // at only works for objects + if (is_object()) + { + // if key is found, return value and given default value otherwise + const auto it = find(key); + if (it != end()) + { + return *it; + } + else + { + return default_value; + } + } + else + { + throw std::domain_error("cannot use value() with " + type_name()); + } + } + + /*! + @brief overload for a default value of type const char* + @copydoc basic_json::value() + */ + string_t value(const typename object_t::key_type& key, const char* default_value) const + { + return value(key, string_t(default_value)); } /*! @@ -2782,6 +3167,8 @@ class basic_json @throw std::out_of_range when called on null value @liveexample{The following code shows an example for @ref front.,front} + + @since version 1.0.0 */ reference front() { @@ -2814,6 +3201,8 @@ class basic_json @throw std::out_of_range when called on null value. @liveexample{The following code shows an example for @ref back.,back} + + @since version 1.0.0 */ reference back() { @@ -2838,7 +3227,7 @@ class basic_json Removes the element specified by iterator @a pos. Invalidates iterators and references at or after the point of the erase, including the end() iterator. The iterator @a pos must be valid and dereferenceable. Thus the - end() iterator (which is valid, but is not dereferencable) cannot be used + end() iterator (which is valid, but is not dereferenceable) cannot be used as a value for @a pos. If called on a primitive type other than null, the resulting JSON value @@ -2850,11 +3239,13 @@ class basic_json @tparam InteratorType an @ref iterator or @ref const_iterator - @throw std::domain_error if called on a `null` value + @throw std::domain_error if called on a `null` value; example: `"cannot use + erase() with null"` @throw std::domain_error if called on an iterator which does not belong to - the current JSON value - @throw std::out_of_range if called on a primitive type with invalid iterator - (i.e., any iterator which is not end()) + the current JSON value; example: `"iterator does not fit current value"` + @throw std::out_of_range if called on a primitive type with invalid + iterator (i.e., any iterator which is not end()); example: `"iterator out + of range"` @complexity The complexity depends on the type: - objects: amortized constant @@ -2864,6 +3255,15 @@ class basic_json @liveexample{The example shows the result of erase for different JSON types.,erase__IteratorType} + + @sa @ref erase(InteratorType, InteratorType) -- removes the elements in the + given range + @sa @ref erase(const typename object_t::key_type&) -- removes the element + from an object at the given key + @sa @ref erase(const size_type) -- removes the element from an array at the + given index + + @since version 1.0.0 */ template erase(pos.m_it.object_iterator); break; } case value_t::array: { + assert(m_value.array != nullptr); result.m_it.array_iterator = m_value.array->erase(pos.m_it.array_iterator); break; } @@ -2942,11 +3344,13 @@ class basic_json @tparam InteratorType an @ref iterator or @ref const_iterator - @throw std::domain_error if called on a `null` value + @throw std::domain_error if called on a `null` value; example: `"cannot use + erase() with null"` @throw std::domain_error if called on iterators which does not belong to - the current JSON value - @throw std::out_of_range if called on a primitive type with invalid iterators - (i.e., if `first != begin()` and `last != end()`) + the current JSON value; example: `"iterators do not fit current value"` + @throw std::out_of_range if called on a primitive type with invalid + iterators (i.e., if `first != begin()` and `last != end()`); example: + `"iterators out of range"` @complexity The complexity depends on the type: - objects: `log(size()) + std::distance(first, last)` @@ -2957,6 +3361,14 @@ class basic_json @liveexample{The example shows the result of erase for different JSON types.,erase__IteratorType_IteratorType} + + @sa @ref erase(InteratorType) -- removes the element at a given position + @sa @ref erase(const typename object_t::key_type&) -- removes the element + from an object at the given key + @sa @ref erase(const size_type) -- removes the element from an array at the + given index + + @since version 1.0.0 */ template erase(first.m_it.object_iterator, last.m_it.object_iterator); break; @@ -3005,6 +3418,7 @@ class basic_json case value_t::array: { + assert(m_value.array != nullptr); result.m_it.array_iterator = m_value.array->erase(first.m_it.array_iterator, last.m_it.array_iterator); break; @@ -3012,7 +3426,7 @@ class basic_json default: { - throw std::domain_error("cannot use erase with " + type_name()); + throw std::domain_error("cannot use erase() with " + type_name()); } } @@ -3030,21 +3444,33 @@ class basic_json type, the return value will always be `0` (@a key was not found) or `1` (@a key was found). - @throw std::domain_error when called on a type other than JSON object + @throw std::domain_error when called on a type other than JSON object; + example: `"cannot use erase() with null"` @complexity `log(size()) + count(key)` @liveexample{The example shows the effect of erase.,erase__key_type} + + @sa @ref erase(InteratorType) -- removes the element at a given position + @sa @ref erase(InteratorType, InteratorType) -- removes the elements in the + given range + @sa @ref erase(const size_type) -- removes the element from an array at the + given index + + @since version 1.0.0 */ size_type erase(const typename object_t::key_type& key) { // this erase only works for objects - if (m_type != value_t::object) + if (is_object()) + { + assert(m_value.object != nullptr); + return m_value.object->erase(key); + } + else { throw std::domain_error("cannot use erase() with " + type_name()); } - - return m_value.object->erase(key); } /*! @@ -3054,27 +3480,40 @@ class basic_json @param[in] idx index of the element to remove - @throw std::domain_error when called on a type other than JSON array - @throw std::out_of_range when `idx >= size()` + @throw std::domain_error when called on a type other than JSON array; + example: `"cannot use erase() with null"` + @throw std::out_of_range when `idx >= size()`; example: `"index out of + range"` @complexity Linear in distance between @a idx and the end of the container. @liveexample{The example shows the effect of erase.,erase__size_type} + + @sa @ref erase(InteratorType) -- removes the element at a given position + @sa @ref erase(InteratorType, InteratorType) -- removes the elements in the + given range + @sa @ref erase(const typename object_t::key_type&) -- removes the element + from an object at the given key + + @since version 1.0.0 */ void erase(const size_type idx) { // this erase only works for arrays - if (m_type != value_t::array) + if (is_array()) + { + if (idx >= size()) + { + throw std::out_of_range("index out of range"); + } + + assert(m_value.array != nullptr); + m_value.array->erase(m_value.array->begin() + static_cast(idx)); + } + else { throw std::domain_error("cannot use erase() with " + type_name()); } - - if (idx >= size()) - { - throw std::out_of_range("index out of range"); - } - - m_value.array->erase(m_value.array->begin() + static_cast(idx)); } /*! @@ -3091,13 +3530,16 @@ class basic_json @complexity Logarithmic in the size of the JSON object. @liveexample{The example shows how find is used.,find__key_type} + + @since version 1.0.0 */ iterator find(typename object_t::key_type key) { auto result = end(); - if (m_type == value_t::object) + if (is_object()) { + assert(m_value.object != nullptr); result.m_it.object_iterator = m_value.object->find(key); } @@ -3112,8 +3554,9 @@ class basic_json { auto result = cend(); - if (m_type == value_t::object) + if (is_object()) { + assert(m_value.object != nullptr); result.m_it.object_iterator = m_value.object->find(key); } @@ -3135,11 +3578,14 @@ class basic_json @complexity Logarithmic in the size of the JSON object. @liveexample{The example shows how count is used.,count} + + @since version 1.0.0 */ size_type count(typename object_t::key_type key) const { // return 0 for all nonobject types - return (m_type == value_t::object) ? m_value.object->count(key) : 0; + assert(not is_object() or m_value.object != nullptr); + return is_object() ? m_value.object->count(key) : 0; } /// @} @@ -3167,6 +3613,8 @@ class basic_json - The complexity is constant. @liveexample{The following code shows an example for @ref begin.,begin} + + @since version 1.0.0 */ iterator begin() { @@ -3199,6 +3647,8 @@ class basic_json - Has the semantics of `const_cast(*this).begin()`. @liveexample{The following code shows an example for @ref cbegin.,cbegin} + + @since version 1.0.0 */ const_iterator cbegin() const { @@ -3222,6 +3672,8 @@ class basic_json - The complexity is constant. @liveexample{The following code shows an example for @ref end.,end} + + @since version 1.0.0 */ iterator end() { @@ -3254,6 +3706,8 @@ class basic_json - Has the semantics of `const_cast(*this).end()`. @liveexample{The following code shows an example for @ref cend.,cend} + + @since version 1.0.0 */ const_iterator cend() const { @@ -3276,6 +3730,8 @@ class basic_json - Has the semantics of `reverse_iterator(end())`. @liveexample{The following code shows an example for @ref rbegin.,rbegin} + + @since version 1.0.0 */ reverse_iterator rbegin() { @@ -3305,6 +3761,8 @@ class basic_json - Has the semantics of `reverse_iterator(begin())`. @liveexample{The following code shows an example for @ref rend.,rend} + + @since version 1.0.0 */ reverse_iterator rend() { @@ -3334,6 +3792,8 @@ class basic_json - Has the semantics of `const_cast(*this).rbegin()`. @liveexample{The following code shows an example for @ref crbegin.,crbegin} + + @since version 1.0.0 */ const_reverse_iterator crbegin() const { @@ -3355,12 +3815,43 @@ class basic_json - Has the semantics of `const_cast(*this).rend()`. @liveexample{The following code shows an example for @ref crend.,crend} + + @since version 1.0.0 */ const_reverse_iterator crend() const { return const_reverse_iterator(cbegin()); } + private: + // forward declaration + template class iteration_proxy; + + public: + /*! + @brief wrapper to access iterator member functions in range-based for + + This function allows to access @ref iterator::key() and @ref + iterator::value() during range-based for loops. In these loops, a reference + to the JSON values is returned, so there is no access to the underlying + iterator. + + @note The name of this function is not yet final and may change in the + future. + */ + static iteration_proxy iterator_wrapper(reference cont) + { + return iteration_proxy(cont); + } + + /*! + @copydoc iterator_wrapper(reference) + */ + static iteration_proxy iterator_wrapper(const_reference cont) + { + return iteration_proxy(cont); + } + /// @} @@ -3388,8 +3879,8 @@ class basic_json array | result of function array_t::empty() @complexity Constant, as long as @ref array_t and @ref object_t satisfy the - Container concept; that is, their empty() functions have - constant complexity. + Container concept; that is, their empty() functions have constant + complexity. @requirement This function satisfies the Container requirements: - The complexity is constant. @@ -3397,23 +3888,28 @@ class basic_json @liveexample{The following code uses @ref empty to check if a @ref json object contains any elements.,empty} + + @since version 1.0.0 */ bool empty() const noexcept { switch (m_type) { - case (value_t::null): + case value_t::null: { + // null values are empty return true; } - case (value_t::array): + case value_t::array: { + assert(m_value.array != nullptr); return m_value.array->empty(); } - case (value_t::object): + case value_t::object: { + assert(m_value.object != nullptr); return m_value.object->empty(); } @@ -3442,8 +3938,7 @@ class basic_json array | result of function array_t::size() @complexity Constant, as long as @ref array_t and @ref object_t satisfy the - Container concept; that is, their size() functions have - constant complexity. + Container concept; that is, their size() functions have constant complexity. @requirement This function satisfies the Container requirements: - The complexity is constant. @@ -3451,23 +3946,28 @@ class basic_json @liveexample{The following code calls @ref size on the different value types.,size} + + @since version 1.0.0 */ size_type size() const noexcept { switch (m_type) { - case (value_t::null): + case value_t::null: { + // null values are empty return 0; } - case (value_t::array): + case value_t::array: { + assert(m_value.array != nullptr); return m_value.array->size(); } - case (value_t::object): + case value_t::object: { + assert(m_value.object != nullptr); return m_value.object->size(); } @@ -3498,8 +3998,8 @@ class basic_json array | result of function array_t::max_size() @complexity Constant, as long as @ref array_t and @ref object_t satisfy the - Container concept; that is, their max_size() functions have - constant complexity. + Container concept; that is, their max_size() functions have constant + complexity. @requirement This function satisfies the Container requirements: - The complexity is constant. @@ -3508,18 +4008,22 @@ class basic_json @liveexample{The following code calls @ref max_size on the different value types. Note the output is implementation specific.,max_size} + + @since version 1.0.0 */ size_type max_size() const noexcept { switch (m_type) { - case (value_t::array): + case value_t::array: { + assert(m_value.array != nullptr); return m_value.array->max_size(); } - case (value_t::object): + case value_t::object: { + assert(m_value.object != nullptr); return m_value.object->max_size(); } @@ -3563,100 +4067,108 @@ class basic_json @liveexample{The example below shows the effect of @ref clear to different JSON types.,clear} + + @since version 1.0.0 */ void clear() noexcept { switch (m_type) { - case (value_t::null): - case (value_t::discarded): - { - break; - } - - case (value_t::number_integer): + case value_t::number_integer: { m_value.number_integer = 0; break; } - case (value_t::number_float): + case value_t::number_float: { m_value.number_float = 0.0; break; } - case (value_t::boolean): + case value_t::boolean: { m_value.boolean = false; break; } - case (value_t::string): + case value_t::string: { + assert(m_value.string != nullptr); m_value.string->clear(); break; } - case (value_t::array): + case value_t::array: { + assert(m_value.array != nullptr); m_value.array->clear(); break; } - case (value_t::object): + case value_t::object: { + assert(m_value.object != nullptr); m_value.object->clear(); break; } + + default: + { + break; + } } } /*! @brief add an object to an array - Appends the given element @a value to the end of the JSON value. If the + Appends the given element @a val to the end of the JSON value. If the function is called on a JSON null value, an empty array is created before - appending @a value. + appending @a val. - @param value the value to add to the JSON array + @param val the value to add to the JSON array - @throw std::domain_error when called on a type other than JSON array or null + @throw std::domain_error when called on a type other than JSON array or + null; example: `"cannot use push_back() with number"` @complexity Amortized constant. @liveexample{The example shows how `push_back` and `+=` can be used to add elements to a JSON array. Note how the `null` value was silently converted to a JSON array.,push_back} + + @since version 1.0.0 */ - void push_back(basic_json&& value) + void push_back(basic_json&& val) { // push_back only works for null objects or arrays - if (not(m_type == value_t::null or m_type == value_t::array)) + if (not(is_null() or is_array())) { throw std::domain_error("cannot use push_back() with " + type_name()); } // transform null object into an array - if (m_type == value_t::null) + if (is_null()) { m_type = value_t::array; m_value = value_t::array; } // add element to array (move semantics) - m_value.array->push_back(std::move(value)); + assert(m_value.array != nullptr); + m_value.array->push_back(std::move(val)); // invalidate object - value.m_type = value_t::null; + val.m_type = value_t::null; } /*! @brief add an object to an array @copydoc push_back(basic_json&&) */ - reference operator+=(basic_json&& value) + reference operator+=(basic_json&& val) { - push_back(std::move(value)); + push_back(std::move(val)); return *this; } @@ -3664,167 +4176,185 @@ class basic_json @brief add an object to an array @copydoc push_back(basic_json&&) */ - void push_back(const basic_json& value) + void push_back(const basic_json& val) { // push_back only works for null objects or arrays - if (not(m_type == value_t::null or m_type == value_t::array)) + if (not(is_null() or is_array())) { throw std::domain_error("cannot use push_back() with " + type_name()); } // transform null object into an array - if (m_type == value_t::null) + if (is_null()) { m_type = value_t::array; m_value = value_t::array; } // add element to array - m_value.array->push_back(value); + assert(m_value.array != nullptr); + m_value.array->push_back(val); } /*! @brief add an object to an array @copydoc push_back(basic_json&&) */ - reference operator+=(const basic_json& value) + reference operator+=(const basic_json& val) { - push_back(value); + push_back(val); return *this; } /*! @brief add an object to an object - Inserts the given element @a value to the JSON object. If the function is + Inserts the given element @a val to the JSON object. If the function is called on a JSON null value, an empty object is created before inserting @a - value. + val. - @param[in] value the value to add to the JSON object + @param[in] val the value to add to the JSON object @throw std::domain_error when called on a type other than JSON object or - null + null; example: `"cannot use push_back() with number"` @complexity Logarithmic in the size of the container, O(log(`size()`)). @liveexample{The example shows how `push_back` and `+=` can be used to add elements to a JSON object. Note how the `null` value was silently converted to a JSON object.,push_back__object_t__value} + + @since version 1.0.0 */ - void push_back(const typename object_t::value_type& value) + void push_back(const typename object_t::value_type& val) { // push_back only works for null objects or objects - if (not(m_type == value_t::null or m_type == value_t::object)) + if (not(is_null() or is_object())) { throw std::domain_error("cannot use push_back() with " + type_name()); } // transform null object into an object - if (m_type == value_t::null) + if (is_null()) { m_type = value_t::object; m_value = value_t::object; } // add element to array - m_value.object->insert(value); + assert(m_value.object != nullptr); + m_value.object->insert(val); } /*! @brief add an object to an object @copydoc push_back(const typename object_t::value_type&) */ - reference operator+=(const typename object_t::value_type& value) + reference operator+=(const typename object_t::value_type& val) { - push_back(value); - return operator[](value.first); + push_back(val); + return operator[](val.first); } /*! @brief inserts element - Inserts element @a value before iterator @a pos. + Inserts element @a val before iterator @a pos. @param[in] pos iterator before which the content will be inserted; may be the end() iterator - @param[in] value element to insert - @return iterator pointing to the inserted @a value. + @param[in] val element to insert + @return iterator pointing to the inserted @a val. - @throw std::domain_error if called on JSON values other than arrays - @throw std::domain_error if @a pos is not an iterator of *this + @throw std::domain_error if called on JSON values other than arrays; + example: `"cannot use insert() with string"` + @throw std::domain_error if @a pos is not an iterator of *this; example: + `"iterator does not fit current value"` @complexity Constant plus linear in the distance between pos and end of the container. @liveexample{The example shows how insert is used.,insert} + + @since version 1.0.0 */ - iterator insert(const_iterator pos, const basic_json& value) + iterator insert(const_iterator pos, const basic_json& val) { // insert only works for arrays - if (m_type != value_t::array) + if (is_array()) + { + // check if iterator pos fits to this JSON value + if (pos.m_object != this) + { + throw std::domain_error("iterator does not fit current value"); + } + + // insert to array and return iterator + iterator result(this); + assert(m_value.array != nullptr); + result.m_it.array_iterator = m_value.array->insert(pos.m_it.array_iterator, val); + return result; + } + else { throw std::domain_error("cannot use insert() with " + type_name()); } - - // check if iterator pos fits to this JSON value - if (pos.m_object != this) - { - throw std::domain_error("iterator does not fit current value"); - } - - // insert to array and return iterator - iterator result(this); - result.m_it.array_iterator = m_value.array->insert(pos.m_it.array_iterator, value); - return result; } /*! @brief inserts element @copydoc insert(const_iterator, const basic_json&) */ - iterator insert(const_iterator pos, basic_json&& value) + iterator insert(const_iterator pos, basic_json&& val) { - return insert(pos, value); + return insert(pos, val); } /*! @brief inserts elements - Inserts @a count copies of @a value before iterator @a pos. + Inserts @a cnt copies of @a val before iterator @a pos. @param[in] pos iterator before which the content will be inserted; may be the end() iterator - @param[in] count number of copies of @a value to insert - @param[in] value element to insert + @param[in] cnt number of copies of @a val to insert + @param[in] val element to insert @return iterator pointing to the first element inserted, or @a pos if - `count==0` + `cnt==0` - @throw std::domain_error if called on JSON values other than arrays - @throw std::domain_error if @a pos is not an iterator of *this + @throw std::domain_error if called on JSON values other than arrays; + example: `"cannot use insert() with string"` + @throw std::domain_error if @a pos is not an iterator of *this; example: + `"iterator does not fit current value"` - @complexity Linear in @a count plus linear in the distance between @a pos + @complexity Linear in @a cnt plus linear in the distance between @a pos and end of the container. @liveexample{The example shows how insert is used.,insert__count} + + @since version 1.0.0 */ - iterator insert(const_iterator pos, size_type count, const basic_json& value) + iterator insert(const_iterator pos, size_type cnt, const basic_json& val) { // insert only works for arrays - if (m_type != value_t::array) + if (is_array()) + { + // check if iterator pos fits to this JSON value + if (pos.m_object != this) + { + throw std::domain_error("iterator does not fit current value"); + } + + // insert to array and return iterator + iterator result(this); + assert(m_value.array != nullptr); + result.m_it.array_iterator = m_value.array->insert(pos.m_it.array_iterator, cnt, val); + return result; + } + else { throw std::domain_error("cannot use insert() with " + type_name()); } - - // check if iterator pos fits to this JSON value - if (pos.m_object != this) - { - throw std::domain_error("iterator does not fit current value"); - } - - // insert to array and return iterator - iterator result(this); - result.m_it.array_iterator = m_value.array->insert(pos.m_it.array_iterator, count, value); - return result; } /*! @@ -3837,12 +4367,16 @@ class basic_json @param[in] first begin of the range of elements to insert @param[in] last end of the range of elements to insert - @throw std::domain_error if called on JSON values other than arrays - @throw std::domain_error if @a pos is not an iterator of *this + @throw std::domain_error if called on JSON values other than arrays; + example: `"cannot use insert() with string"` + @throw std::domain_error if @a pos is not an iterator of *this; example: + `"iterator does not fit current value"` @throw std::domain_error if @a first and @a last do not belong to the same - JSON value + JSON value; example: `"iterators do not fit"` @throw std::domain_error if @a first or @a last are iterators into - container for which insert is called + container for which insert is called; example: `"passed iterators may not + belong to container"` + @return iterator pointing to the first element inserted, or @a pos if `first==last` @@ -3850,11 +4384,13 @@ class basic_json distance between @a pos and end of the container. @liveexample{The example shows how insert is used.,insert__range} + + @since version 1.0.0 */ iterator insert(const_iterator pos, const_iterator first, const_iterator last) { // insert only works for arrays - if (m_type != value_t::array) + if (not is_array()) { throw std::domain_error("cannot use insert() with " + type_name()); } @@ -3867,7 +4403,7 @@ class basic_json if (first.m_object != last.m_object) { - throw std::domain_error("iterators does not fit"); + throw std::domain_error("iterators do not fit"); } if (first.m_object == this or last.m_object == this) @@ -3877,8 +4413,11 @@ class basic_json // insert to array and return iterator iterator result(this); - result.m_it.array_iterator = m_value.array->insert(pos.m_it.array_iterator, - first.m_it.array_iterator, last.m_it.array_iterator); + assert(m_value.array != nullptr); + result.m_it.array_iterator = m_value.array->insert( + pos.m_it.array_iterator, + first.m_it.array_iterator, + last.m_it.array_iterator); return result; } @@ -3891,8 +4430,11 @@ class basic_json the end() iterator @param[in] ilist initializer list to insert the values from - @throw std::domain_error if called on JSON values other than arrays - @throw std::domain_error if @a pos is not an iterator of *this + @throw std::domain_error if called on JSON values other than arrays; + example: `"cannot use insert() with string"` + @throw std::domain_error if @a pos is not an iterator of *this; example: + `"iterator does not fit current value"` + @return iterator pointing to the first element inserted, or @a pos if `ilist` is empty @@ -3900,11 +4442,13 @@ class basic_json pos and end of the container. @liveexample{The example shows how insert is used.,insert__ilist} + + @since version 1.0.0 */ iterator insert(const_iterator pos, std::initializer_list ilist) { // insert only works for arrays - if (m_type != value_t::array) + if (not is_array()) { throw std::domain_error("cannot use insert() with " + type_name()); } @@ -3917,6 +4461,7 @@ class basic_json // insert to array and return iterator iterator result(this); + assert(m_value.array != nullptr); result.m_it.array_iterator = m_value.array->insert(pos.m_it.array_iterator, ilist); return result; } @@ -3935,6 +4480,8 @@ class basic_json @liveexample{The example below shows how JSON arrays can be swapped.,swap__reference} + + @since version 1.0.0 */ void swap(reference other) noexcept ( std::is_nothrow_move_constructible::value and @@ -3957,23 +4504,28 @@ class basic_json @param[in,out] other array to exchange the contents with - @throw std::domain_error when JSON value is not an array + @throw std::domain_error when JSON value is not an array; example: `"cannot + use swap() with string"` @complexity Constant. @liveexample{The example below shows how JSON values can be swapped.,swap__array_t} + + @since version 1.0.0 */ void swap(array_t& other) { // swap only works for arrays - if (m_type != value_t::array) + if (is_array()) + { + assert(m_value.array != nullptr); + std::swap(*(m_value.array), other); + } + else { throw std::domain_error("cannot use swap() with " + type_name()); } - - // swap arrays - std::swap(*(m_value.array), other); } /*! @@ -3986,23 +4538,28 @@ class basic_json @param[in,out] other object to exchange the contents with - @throw std::domain_error when JSON value is not an object + @throw std::domain_error when JSON value is not an object; example: + `"cannot use swap() with string"` @complexity Constant. @liveexample{The example below shows how JSON values can be swapped.,swap__object_t} + + @since version 1.0.0 */ void swap(object_t& other) { // swap only works for objects - if (m_type != value_t::object) + if (is_object()) + { + assert(m_value.object != nullptr); + std::swap(*(m_value.object), other); + } + else { throw std::domain_error("cannot use swap() with " + type_name()); } - - // swap objects - std::swap(*(m_value.object), other); } /*! @@ -4015,23 +4572,28 @@ class basic_json @param[in,out] other string to exchange the contents with - @throw std::domain_error when JSON value is not a string + @throw std::domain_error when JSON value is not a string; example: `"cannot + use swap() with boolean"` @complexity Constant. @liveexample{The example below shows how JSON values can be swapped.,swap__string_t} + + @since version 1.0.0 */ void swap(string_t& other) { // swap only works for strings - if (m_type != value_t::string) + if (is_string()) + { + assert(m_value.string != nullptr); + std::swap(*(m_value.string), other); + } + else { throw std::domain_error("cannot use swap() with " + type_name()); } - - // swap strings - std::swap(*(m_value.string), other); } /// @} @@ -4051,6 +4613,8 @@ class basic_json 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 */ friend bool operator<(const value_t lhs, const value_t rhs) { @@ -4095,6 +4659,8 @@ class basic_json @liveexample{The example demonstrates comparing several JSON types.,operator__equal} + + @since version 1.0.0 */ friend bool operator==(const_reference lhs, const_reference rhs) noexcept { @@ -4105,22 +4671,44 @@ class basic_json { switch (lhs_type) { - case (value_t::array): + case value_t::array: + { + assert(lhs.m_value.array != nullptr); + assert(rhs.m_value.array != nullptr); return *lhs.m_value.array == *rhs.m_value.array; - case (value_t::object): + } + case value_t::object: + { + assert(lhs.m_value.object != nullptr); + assert(rhs.m_value.object != nullptr); return *lhs.m_value.object == *rhs.m_value.object; - case (value_t::null): + } + case value_t::null: + { return true; - case (value_t::string): + } + case value_t::string: + { + assert(lhs.m_value.string != nullptr); + assert(rhs.m_value.string != nullptr); return *lhs.m_value.string == *rhs.m_value.string; - case (value_t::boolean): + } + case value_t::boolean: + { return lhs.m_value.boolean == rhs.m_value.boolean; - case (value_t::number_integer): + } + case value_t::number_integer: + { return lhs.m_value.number_integer == rhs.m_value.number_integer; - case (value_t::number_float): + } + case value_t::number_float: + { return approx(lhs.m_value.number_float, rhs.m_value.number_float); - case (value_t::discarded): + } + default: + { return false; + } } } else if (lhs_type == value_t::number_integer and rhs_type == value_t::number_float) @@ -4151,6 +4739,8 @@ class basic_json @liveexample{The example compares several JSON types to the null pointer. ,operator__equal__nullptr_t} + + @since version 1.0.0 */ friend bool operator==(const_reference v, std::nullptr_t) noexcept { @@ -4179,6 +4769,8 @@ class basic_json @liveexample{The example demonstrates comparing several JSON types.,operator__notequal} + + @since version 1.0.0 */ friend bool operator!=(const_reference lhs, const_reference rhs) noexcept { @@ -4200,6 +4792,8 @@ class basic_json @liveexample{The example compares several JSON types to the null pointer. ,operator__notequal__nullptr_t} + + @since version 1.0.0 */ friend bool operator!=(const_reference v, std::nullptr_t) noexcept { @@ -4236,6 +4830,8 @@ class basic_json @liveexample{The example demonstrates comparing several JSON types.,operator__less} + + @since version 1.0.0 */ friend bool operator<(const_reference lhs, const_reference rhs) noexcept { @@ -4246,22 +4842,44 @@ class basic_json { switch (lhs_type) { - case (value_t::array): + case value_t::array: + { + assert(lhs.m_value.array != nullptr); + assert(rhs.m_value.array != nullptr); return *lhs.m_value.array < *rhs.m_value.array; - case (value_t::object): + } + case value_t::object: + { + assert(lhs.m_value.object != nullptr); + assert(rhs.m_value.object != nullptr); return *lhs.m_value.object < *rhs.m_value.object; - case (value_t::null): + } + case value_t::null: + { return false; - case (value_t::string): + } + case value_t::string: + { + assert(lhs.m_value.string != nullptr); + assert(rhs.m_value.string != nullptr); return *lhs.m_value.string < *rhs.m_value.string; - case (value_t::boolean): + } + case value_t::boolean: + { return lhs.m_value.boolean < rhs.m_value.boolean; - case (value_t::number_integer): + } + case value_t::number_integer: + { return lhs.m_value.number_integer < rhs.m_value.number_integer; - case (value_t::number_float): + } + case value_t::number_float: + { return lhs.m_value.number_float < rhs.m_value.number_float; - case (value_t::discarded): + } + default: + { return false; + } } } else if (lhs_type == value_t::number_integer and rhs_type == value_t::number_float) @@ -4295,6 +4913,8 @@ class basic_json @liveexample{The example demonstrates comparing several JSON types.,operator__greater} + + @since version 1.0.0 */ friend bool operator<=(const_reference lhs, const_reference rhs) noexcept { @@ -4315,6 +4935,8 @@ class basic_json @liveexample{The example demonstrates comparing several JSON types.,operator__lessequal} + + @since version 1.0.0 */ friend bool operator>(const_reference lhs, const_reference rhs) noexcept { @@ -4335,6 +4957,8 @@ class basic_json @liveexample{The example demonstrates comparing several JSON types.,operator__greaterequal} + + @since version 1.0.0 */ friend bool operator>=(const_reference lhs, const_reference rhs) noexcept { @@ -4370,6 +4994,8 @@ class basic_json @liveexample{The example below shows the serialization with different parameters to `width` to adjust the indentation level.,operator_serialize} + + @since version 1.0.0 */ friend std::ostream& operator<<(std::ostream& o, const basic_json& j) { @@ -4418,11 +5044,15 @@ class basic_json LL(1) parser. The complexity can be higher if the parser callback function @a cb has a super-linear complexity. + @note A UTF-8 byte order mark is silently ignored. + @liveexample{The example below demonstrates the parse function with and without callback function.,parse__string__parser_callback_t} - @sa parse(std::istream&, parser_callback_t) for a version that reads from - an input stream + @sa @ref parse(std::istream&, parser_callback_t) for a version that reads + from an input stream + + @since version 1.0.0 */ static basic_json parse(const string_t& s, parser_callback_t cb = nullptr) { @@ -4443,17 +5073,24 @@ class basic_json LL(1) parser. The complexity can be higher if the parser callback function @a cb has a super-linear complexity. + @note A UTF-8 byte order mark is silently ignored. + @liveexample{The example below demonstrates the parse function with and without callback function.,parse__istream__parser_callback_t} - @sa parse(const string_t&, parser_callback_t) for a version that reads + @sa @ref parse(const string_t&, parser_callback_t) for a version that reads from a string + + @since version 1.0.0 */ static basic_json parse(std::istream& i, parser_callback_t cb = nullptr) { return parser(i, cb).parse(); } + /*! + @copydoc parse(std::istream&, parser_callback_t) + */ static basic_json parse(std::istream&& i, parser_callback_t cb = nullptr) { return parser(i, cb).parse(); @@ -4472,11 +5109,15 @@ class basic_json @complexity Linear in the length of the input. The parser is a predictive LL(1) parser. + @note A UTF-8 byte order mark is silently ignored. + @liveexample{The example below shows how a JSON value is constructed by reading a serialization from a stream.,operator_deserialize} @sa parse(std::istream&, parser_callback_t) for a variant with a parser callback function to filter values while parsing + + @since version 1.0.0 */ friend std::istream& operator<<(basic_json& j, std::istream& i) { @@ -4507,40 +5148,20 @@ class basic_json { switch (m_type) { - case (value_t::null): - { + case value_t::null: return "null"; - } - - case (value_t::object): - { + case value_t::object: return "object"; - } - - case (value_t::array): - { + case value_t::array: return "array"; - } - - case (value_t::string): - { + case value_t::string: return "string"; - } - - case (value_t::boolean): - { + case value_t::boolean: return "boolean"; - } - - case (value_t::discarded): - { + case value_t::discarded: return "discarded"; - } - default: - { return "number"; - } } } @@ -4677,11 +5298,21 @@ class basic_json { if (c >= 0x00 and c <= 0x1f) { + // convert a number 0..15 to its hex representation (0..f) + auto hexify = [](const char v) -> char + { + return (v < 10) ? ('0' + v) : ('a' + v - 10); + }; + // print character c as \uxxxx - sprintf(&result[pos + 1], "u%04x", int(c)); - pos += 6; - // overwrite trailing null character - result[pos] = '\\'; + for (const char m : + { 'u', '0', '0', hexify(c >> 4), hexify(c & 0x0f) + }) + { + result[++pos] = m; + } + + ++pos; } else { @@ -4700,12 +5331,12 @@ class basic_json @brief internal implementation of the serialization function This function is called by the public member function dump and organizes - the serializaion internally. The indentation level is propagated as + the serialization internally. The indentation level is propagated as additional parameter. In case of arrays and objects, the function is called recursively. Note that - strings and object keys are escaped using escape_string() - - integer numbers are converted implictly via operator<< + - integer numbers are converted implicitly via operator<< - floating-point numbers are converted to a string using "%g" format @param[out] o stream to write to @@ -4713,7 +5344,9 @@ class basic_json @param[in] indent_step the indent level @param[in] current_indent the current indent level (only used internally) */ - void dump(std::ostream& o, const bool pretty_print, const unsigned int indent_step, + void dump(std::ostream& o, + const bool pretty_print, + const unsigned int indent_step, const unsigned int current_indent = 0) const { // variable to hold indentation for recursive calls @@ -4721,8 +5354,10 @@ class basic_json switch (m_type) { - case (value_t::object): + case value_t::object: { + assert(m_value.object != nullptr); + if (m_value.object->empty()) { o << "{}"; @@ -4761,8 +5396,10 @@ class basic_json return; } - case (value_t::array): + case value_t::array: { + assert(m_value.array != nullptr); + if (m_value.array->empty()) { o << "[]"; @@ -4799,25 +5436,26 @@ class basic_json return; } - case (value_t::string): + case value_t::string: { + assert(m_value.string != nullptr); o << string_t("\"") << escape_string(*m_value.string) << "\""; return; } - case (value_t::boolean): + case value_t::boolean: { o << (m_value.boolean ? "true" : "false"); return; } - case (value_t::number_integer): + case value_t::number_integer: { o << m_value.number_integer; return; } - case (value_t::number_float): + case value_t::number_float: { // 15 digits of precision allows round-trip IEEE 754 // string->double->string; to be safe, we read this value from @@ -4826,13 +5464,13 @@ class basic_json return; } - case (value_t::discarded): + case value_t::discarded: { o << ""; return; } - default: + case value_t::null: { o << "null"; return; @@ -4910,7 +5548,7 @@ class basic_json static constexpr difference_type end_value = begin_value + 1; /// iterator as signed integer type - difference_type m_it = std::numeric_limits::min(); + difference_type m_it = std::numeric_limits::denorm_min(); }; /*! @@ -4935,6 +5573,102 @@ class basic_json {} }; + /// proxy class for the iterator_wrapper functions + template + class iteration_proxy + { + private: + /// helper class for iteration + class iteration_proxy_internal + { + private: + /// the iterator + IteratorType anchor; + /// an index for arrays (used to create key names) + size_t array_index = 0; + + public: + iteration_proxy_internal(IteratorType it) + : anchor(it) + {} + + /// dereference operator (needed for range-based for) + iteration_proxy_internal& operator*() + { + return *this; + } + + /// increment operator (needed for range-based for) + iteration_proxy_internal& operator++() + { + ++anchor; + ++array_index; + + return *this; + } + + /// inequality operator (needed for range-based for) + bool operator!= (const iteration_proxy_internal& o) const + { + return anchor != o.anchor; + } + + /// return key of the iterator + typename basic_json::string_t key() const + { + assert(anchor.m_object != nullptr); + + switch (anchor.m_object->type()) + { + // use integer array index as key + case value_t::array: + { + return std::to_string(array_index); + } + + // use key from the object + case value_t::object: + { + return anchor.key(); + } + + // use an empty key for all primitive types + default: + { + return ""; + } + } + } + + /// return value of the iterator + typename IteratorType::reference value() const + { + return anchor.value(); + } + }; + + /// the container to iterate + typename IteratorType::reference container; + + public: + /// construct iteration proxy from a container + iteration_proxy(typename IteratorType::reference cont) + : container(cont) + {} + + /// return iterator begin (needed for range-based for) + iteration_proxy_internal begin() + { + return iteration_proxy_internal(container.begin()); + } + + /// return iterator end (needed for range-based for) + iteration_proxy_internal end() + { + return iteration_proxy_internal(container.end()); + } + }; + public: /*! @brief a const random access iterator for the @ref basic_json class @@ -4946,6 +5680,8 @@ class basic_json - [RandomAccessIterator](http://en.cppreference.com/w/cpp/concept/RandomAccessIterator): The iterator that can be moved to point (forward and backward) to any element in constant time. + + @since version 1.0.0 */ class const_iterator : public std::iterator { @@ -4970,18 +5706,22 @@ class basic_json /// constructor for a given JSON instance const_iterator(pointer object) : m_object(object) { + assert(m_object != nullptr); + switch (m_object->m_type) { - case (basic_json::value_t::object): + case basic_json::value_t::object: { m_it.object_iterator = typename object_t::iterator(); break; } - case (basic_json::value_t::array): + + case basic_json::value_t::array: { m_it.array_iterator = typename array_t::iterator(); break; } + default: { m_it.primitive_iterator = primitive_iterator_t(); @@ -4993,15 +5733,17 @@ class basic_json /// copy constructor given a nonconst iterator const_iterator(const iterator& other) : m_object(other.m_object) { + assert(m_object != nullptr); + switch (m_object->m_type) { - case (basic_json::value_t::object): + case basic_json::value_t::object: { m_it.object_iterator = other.m_it.object_iterator; break; } - case (basic_json::value_t::array): + case basic_json::value_t::array: { m_it.array_iterator = other.m_it.array_iterator; break; @@ -5037,21 +5779,25 @@ class basic_json /// set the iterator to the first value void set_begin() { + assert(m_object != nullptr); + switch (m_object->m_type) { - case (basic_json::value_t::object): + case basic_json::value_t::object: { + assert(m_object->m_value.object != nullptr); m_it.object_iterator = m_object->m_value.object->begin(); break; } - case (basic_json::value_t::array): + case basic_json::value_t::array: { + assert(m_object->m_value.array != nullptr); m_it.array_iterator = m_object->m_value.array->begin(); break; } - case (basic_json::value_t::null): + case basic_json::value_t::null: { // set to end so begin()==end() is true: null is empty m_it.primitive_iterator.set_end(); @@ -5069,16 +5815,20 @@ class basic_json /// set the iterator past the last value void set_end() { + assert(m_object != nullptr); + switch (m_object->m_type) { - case (basic_json::value_t::object): + case basic_json::value_t::object: { + assert(m_object->m_value.object != nullptr); m_it.object_iterator = m_object->m_value.object->end(); break; } - case (basic_json::value_t::array): + case basic_json::value_t::array: { + assert(m_object->m_value.array != nullptr); m_it.array_iterator = m_object->m_value.array->end(); break; } @@ -5095,19 +5845,25 @@ class basic_json /// return a reference to the value pointed to by the iterator reference operator*() const { + assert(m_object != nullptr); + switch (m_object->m_type) { - case (basic_json::value_t::object): + case basic_json::value_t::object: { + assert(m_object->m_value.object); + assert(m_it.object_iterator != m_object->m_value.object->end()); return m_it.object_iterator->second; } - case (basic_json::value_t::array): + case basic_json::value_t::array: { + assert(m_object->m_value.array); + assert(m_it.array_iterator != m_object->m_value.array->end()); return *m_it.array_iterator; } - case (basic_json::value_t::null): + case basic_json::value_t::null: { throw std::out_of_range("cannot get value"); } @@ -5129,15 +5885,21 @@ class basic_json /// dereference the iterator pointer operator->() const { + assert(m_object != nullptr); + switch (m_object->m_type) { - case (basic_json::value_t::object): + case basic_json::value_t::object: { + assert(m_object->m_value.object); + assert(m_it.object_iterator != m_object->m_value.object->end()); return &(m_it.object_iterator->second); } - case (basic_json::value_t::array): + case basic_json::value_t::array: { + assert(m_object->m_value.array); + assert(m_it.array_iterator != m_object->m_value.array->end()); return &*m_it.array_iterator; } @@ -5160,22 +5922,23 @@ class basic_json { auto result = *this; ++(*this); - return result; } /// pre-increment (++it) const_iterator& operator++() { + assert(m_object != nullptr); + switch (m_object->m_type) { - case (basic_json::value_t::object): + case basic_json::value_t::object: { ++m_it.object_iterator; break; } - case (basic_json::value_t::array): + case basic_json::value_t::array: { ++m_it.array_iterator; break; @@ -5196,22 +5959,23 @@ class basic_json { auto result = *this; --(*this); - return result; } /// pre-decrement (--it) const_iterator& operator--() { + assert(m_object != nullptr); + switch (m_object->m_type) { - case (basic_json::value_t::object): + case basic_json::value_t::object: { --m_it.object_iterator; break; } - case (basic_json::value_t::array): + case basic_json::value_t::array: { --m_it.array_iterator; break; @@ -5236,14 +6000,16 @@ class basic_json throw std::domain_error("cannot compare iterators of different containers"); } + assert(m_object != nullptr); + switch (m_object->m_type) { - case (basic_json::value_t::object): + case basic_json::value_t::object: { return (m_it.object_iterator == other.m_it.object_iterator); } - case (basic_json::value_t::array): + case basic_json::value_t::array: { return (m_it.array_iterator == other.m_it.array_iterator); } @@ -5270,14 +6036,16 @@ class basic_json throw std::domain_error("cannot compare iterators of different containers"); } + assert(m_object != nullptr); + switch (m_object->m_type) { - case (basic_json::value_t::object): + case basic_json::value_t::object: { - throw std::domain_error("cannot use operator< for object iterators"); + throw std::domain_error("cannot compare order of object iterators"); } - case (basic_json::value_t::array): + case basic_json::value_t::array: { return (m_it.array_iterator < other.m_it.array_iterator); } @@ -5310,14 +6078,16 @@ class basic_json /// add to iterator const_iterator& operator+=(difference_type i) { + assert(m_object != nullptr); + switch (m_object->m_type) { - case (basic_json::value_t::object): + case basic_json::value_t::object: { - throw std::domain_error("cannot use operator+= for object iterators"); + throw std::domain_error("cannot use offsets with object iterators"); } - case (basic_json::value_t::array): + case basic_json::value_t::array: { m_it.array_iterator += i; break; @@ -5358,14 +6128,16 @@ class basic_json /// return difference difference_type operator-(const const_iterator& other) const { + assert(m_object != nullptr); + switch (m_object->m_type) { - case (basic_json::value_t::object): + case basic_json::value_t::object: { - throw std::domain_error("cannot use operator- for object iterators"); + throw std::domain_error("cannot use offsets with object iterators"); } - case (basic_json::value_t::array): + case basic_json::value_t::array: { return m_it.array_iterator - other.m_it.array_iterator; } @@ -5380,19 +6152,21 @@ class basic_json /// access to successor reference operator[](difference_type n) const { + assert(m_object != nullptr); + switch (m_object->m_type) { - case (basic_json::value_t::object): + case basic_json::value_t::object: { throw std::domain_error("cannot use operator[] for object iterators"); } - case (basic_json::value_t::array): + case basic_json::value_t::array: { return *(m_it.array_iterator + n); } - case (basic_json::value_t::null): + case basic_json::value_t::null: { throw std::out_of_range("cannot get value"); } @@ -5414,17 +6188,15 @@ class basic_json /// return the key of an object iterator typename object_t::key_type key() const { - switch (m_object->m_type) - { - case (basic_json::value_t::object): - { - return m_it.object_iterator->first; - } + assert(m_object != nullptr); - default: - { - throw std::domain_error("cannot use key() for non-object iterators"); - } + if (m_object->is_object()) + { + return m_it.object_iterator->first; + } + else + { + throw std::domain_error("cannot use key() for non-object iterators"); } } @@ -5450,6 +6222,8 @@ class basic_json element in constant time. - [OutputIterator](http://en.cppreference.com/w/cpp/concept/OutputIterator): It is possible to write to the pointed-to element. + + @since version 1.0.0 */ class iterator : public const_iterator { @@ -5462,7 +6236,8 @@ class basic_json iterator() = default; /// constructor for a given JSON instance - iterator(pointer object) noexcept : base_iterator(object) + iterator(pointer object) noexcept + : base_iterator(object) {} /// copy constructor @@ -5586,6 +6361,8 @@ class basic_json - [OutputIterator](http://en.cppreference.com/w/cpp/concept/OutputIterator): It is possible to write to the pointed-to element (only if @a Base is @ref iterator). + + @since version 1.0.0 */ template class json_reverse_iterator : public std::reverse_iterator @@ -5598,10 +6375,13 @@ class basic_json /// create reverse iterator from iterator json_reverse_iterator(const typename base_iterator::iterator_type& it) - : base_iterator(it) {} + : base_iterator(it) + {} /// create reverse iterator from base class - json_reverse_iterator(const base_iterator& it) : base_iterator(it) {} + json_reverse_iterator(const base_iterator& it) + : base_iterator(it) + {} /// post-increment (it++) json_reverse_iterator operator++(int) @@ -5679,112 +6459,6 @@ class basic_json } }; - /*! - @brief wrapper to access iterator member functions in range-based for - - This class allows to access @ref key() and @ref value() during range-based - for loops. In these loops, a reference to the JSON values is returned, so - there is no access to the underlying iterator. - */ - class iterator_wrapper - { - private: - /// the container to iterate - basic_json& container; - /// the type of the iterator to use while iteration - using json_iterator = decltype(std::begin(container)); - - /// internal iterator wrapper - class iterator_wrapper_internal - { - private: - /// the iterator - json_iterator anchor; - /// an index for arrays - size_t array_index = 0; - - public: - /// construct wrapper given an iterator - iterator_wrapper_internal(json_iterator i) : anchor(i) - {} - - /// dereference operator (needed for range-based for) - iterator_wrapper_internal& operator*() - { - return *this; - } - - /// increment operator (needed for range-based for) - iterator_wrapper_internal& operator++() - { - ++anchor; - ++array_index; - - return *this; - } - - /// inequality operator (needed for range-based for) - bool operator!= (const iterator_wrapper_internal& o) - { - return anchor != o.anchor; - } - - /// stream operator - friend std::ostream& operator<<(std::ostream& o, const iterator_wrapper_internal& w) - { - return o << w.value(); - } - - /// return key of the iterator - typename basic_json::string_t key() const - { - switch (anchor.m_object->type()) - { - /// use integer array index as key - case (value_t::array): - { - return std::to_string(array_index); - } - - /// use key from the object - case (value_t::object): - { - return anchor.key(); - } - - /// use an empty key for all primitive types - default: - { - return ""; - } - } - } - - /// return value of the iterator - typename json_iterator::reference value() const - { - return anchor.value(); - } - }; - - public: - /// construct iterator wrapper from a container - iterator_wrapper(basic_json& cont) - : container(cont) - {} - - /// return iterator begin (needed for range-based for) - iterator_wrapper_internal begin() - { - return iterator_wrapper_internal(container.begin()); - } - - /// return iterator end (needed for range-based for) - iterator_wrapper_internal end() - { - return iterator_wrapper_internal(container.end()); - } - }; private: ////////////////////// @@ -5808,8 +6482,8 @@ class basic_json 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_number, ///< a number - use get_number() for actual value + value_string, ///< a string -- use get_string() for actual value + value_number, ///< a number -- use get_number() for actual value begin_array, ///< the character for array begin "[" begin_object, ///< the character for object begin "{" end_array, ///< the character for array end "]" @@ -5828,14 +6502,19 @@ class basic_json : m_stream(nullptr), m_buffer(s) { m_content = reinterpret_cast(s.c_str()); + assert(m_content != nullptr); m_start = m_cursor = m_content; m_limit = m_content + s.size(); } + + /// constructor with a given stream explicit lexer(std::istream* s) noexcept : m_stream(s), m_buffer() { + assert(m_stream != nullptr); getline(*m_stream, m_buffer); m_content = reinterpret_cast(m_buffer.c_str()); + assert(m_content != nullptr); m_start = m_cursor = m_content; m_limit = m_content + m_buffer.size(); } @@ -5843,7 +6522,7 @@ class basic_json /// default constructor lexer() = default; - // switch of unwanted functions + // switch off unwanted functions lexer(const lexer&) = delete; lexer operator=(const lexer&) = delete; @@ -5852,9 +6531,13 @@ class basic_json @param[in] codepoint1 the code point (can be high surrogate) @param[in] codepoint2 the code point (can be low surrogate or 0) + @return string representation of the code point - @throw std::out_of_range if code point is >0x10ffff - @throw std::invalid_argument if the low surrogate is invalid + + @throw std::out_of_range if code point is >0x10ffff; example: `"code + points above 0x10FFFF are invalid"` + @throw std::invalid_argument if the low surrogate is invalid; example: + `""missing or wrong low surrogate""` @see */ @@ -5878,7 +6561,7 @@ class basic_json // 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 substract with: + // in the result so we have to subtract with: // (0xD800 << 10) + DC00 - 0x10000 = 0x35FDC00 - 0x35FDC00; } @@ -5922,39 +6605,44 @@ class basic_json return result; } - /// return name of values of type token_type + /// return name of values of type token_type (only used for errors) static std::string token_type_name(token_type t) { switch (t) { - case (token_type::uninitialized): + case token_type::uninitialized: return ""; - case (token_type::literal_true): + case token_type::literal_true: return "true literal"; - case (token_type::literal_false): + case token_type::literal_false: return "false literal"; - case (token_type::literal_null): + case token_type::literal_null: return "null literal"; - case (token_type::value_string): + case token_type::value_string: return "string literal"; - case (token_type::value_number): + case token_type::value_number: 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::end_of_input): - return ""; - default: + 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 ""; + case token_type::end_of_input: + return "end of input"; + default: + { + // catch non-enum values + return "unknown token"; // LCOV_EXCL_LINE + } } } @@ -5975,22 +6663,27 @@ class basic_json // remember the begin of the token m_start = m_cursor; + assert(m_start != nullptr); /*!re2c - re2c:define:YYCTYPE = lexer_char_t; - re2c:define:YYCURSOR = m_cursor; - re2c:define:YYLIMIT = m_limit; - re2c:define:YYMARKER = m_marker; - re2c:define:YYFILL = "yyfill(); // LCOV_EXCL_LINE"; + re2c:define:YYCTYPE = lexer_char_t; + re2c:define:YYCURSOR = m_cursor; + re2c:define:YYLIMIT = m_limit; + re2c:define:YYMARKER = m_marker; + re2c:define:YYFILL = "yyfill(); // LCOV_EXCL_LINE"; re2c:yyfill:parameter = 0; - re2c:indent:string = " "; - re2c:indent:top = 1; - re2c:labelprefix = "basic_json_parser_"; + re2c:indent:string = " "; + re2c:indent:top = 1; + re2c:labelprefix = "basic_json_parser_"; - // whitespace + // ignore whitespace ws = [ \t\n\r]+; ws { return scan(); } + // ignore byte-order-mark + bom = "\xEF\xBB\xBF"; + bom { return scan(); } + // structural characters "[" { return token_type::begin_array; } "]" { return token_type::end_array; } @@ -6041,7 +6734,7 @@ class basic_json /// append data from the stream to the internal buffer void yyfill() noexcept { - if (not m_stream or not * m_stream) + if (m_stream == nullptr or not * m_stream) { return; } @@ -6052,10 +6745,12 @@ class basic_json m_buffer.erase(0, static_cast(offset_start)); std::string line; + assert(m_stream != nullptr); std::getline(*m_stream, line); m_buffer += "\n" + line; // add line with newline symbol m_content = reinterpret_cast(m_buffer.c_str()); + assert(m_content != nullptr); m_start = m_content; m_marker = m_start + offset_marker; m_cursor = m_start + offset_cursor; @@ -6065,6 +6760,7 @@ class basic_json /// return string representation of last read token string_t get_token() const noexcept { + assert(m_start != nullptr); return string_t(reinterpret_cast(m_start), static_cast(m_cursor - m_start)); } @@ -6168,8 +6864,8 @@ class basic_json auto codepoint2 = std::strtoul(std::string(reinterpret_cast (i + 7), 4).c_str(), nullptr, 16); result += to_unicode(codepoint, codepoint2); - // skip the next 11 characters (xxxx\uyyyy) - i += 11; + // skip the next 10 characters (xxxx\uyyyy) + i += 10; } else { @@ -6214,6 +6910,7 @@ class basic_json { // conversion typename string_t::value_type* endptr; + assert(m_start != nullptr); const auto float_val = std::strtold(reinterpret_cast(m_start), &endptr); @@ -6224,7 +6921,7 @@ class basic_json private: /// optional input stream - std::istream* m_stream; + std::istream* m_stream = nullptr; /// the buffer string_t m_buffer; /// the buffer pointer @@ -6241,6 +6938,8 @@ class basic_json /*! @brief syntax analysis + + This class implements a recursive decent parser. */ class parser { @@ -6281,7 +6980,7 @@ class basic_json switch (last_token) { - case (lexer::token_type::begin_object): + case lexer::token_type::begin_object: { if (keep and (not callback or (keep = callback(depth++, parse_event_t::object_start, result)))) { @@ -6359,7 +7058,7 @@ class basic_json return result; } - case (lexer::token_type::begin_array): + case lexer::token_type::begin_array: { if (keep and (not callback or (keep = callback(depth++, parse_event_t::array_start, result)))) { @@ -6414,14 +7113,14 @@ class basic_json return result; } - case (lexer::token_type::literal_null): + case lexer::token_type::literal_null: { get_token(); result.m_type = value_t::null; break; } - case (lexer::token_type::value_string): + case lexer::token_type::value_string: { const auto s = m_lexer.get_string(); get_token(); @@ -6429,7 +7128,7 @@ class basic_json break; } - case (lexer::token_type::literal_true): + case lexer::token_type::literal_true: { get_token(); result.m_type = value_t::boolean; @@ -6437,7 +7136,7 @@ class basic_json break; } - case (lexer::token_type::literal_false): + case lexer::token_type::literal_false: { get_token(); result.m_type = value_t::boolean; @@ -6445,7 +7144,7 @@ class basic_json break; } - case (lexer::token_type::value_number): + case lexer::token_type::value_number: { auto float_val = m_lexer.get_number(); @@ -6463,13 +7162,13 @@ class basic_json const auto int_val = static_cast(float_val); if (approx(float_val, static_cast(int_val))) { - // we basic_json not lose precision -> return int + // we would not lose precision -> return int result.m_type = value_t::number_integer; result.m_value = int_val; } else { - // we would lose precision -> returnfloat + // we would lose precision -> return float result.m_type = value_t::number_float; result.m_value = static_cast(float_val); } @@ -6501,10 +7200,10 @@ class basic_json { if (t != last_token) { - std::string error_msg = "parse error - unexpected \'"; - error_msg += m_lexer.get_token(); - error_msg += "\' (" + lexer::token_type_name(last_token); - error_msg += "); expected " + lexer::token_type_name(t); + std::string error_msg = "parse error - unexpected "; + error_msg += (last_token == lexer::token_type::parse_error ? ("'" + m_lexer.get_token() + "'") : + lexer::token_type_name(last_token)); + error_msg += "; expected " + lexer::token_type_name(t); throw std::invalid_argument(error_msg); } } @@ -6513,10 +7212,9 @@ class basic_json { if (t == last_token) { - std::string error_msg = "parse error - unexpected \'"; - error_msg += m_lexer.get_token(); - error_msg += "\' ("; - error_msg += lexer::token_type_name(last_token) + ")"; + std::string error_msg = "parse error - unexpected "; + error_msg += (last_token == lexer::token_type::parse_error ? ("'" + m_lexer.get_token() + "'") : + lexer::token_type_name(last_token)); throw std::invalid_argument(error_msg); } } @@ -6543,6 +7241,8 @@ class basic_json This type is the default specialization of the @ref basic_json class which uses the standard template types. + +@since version 1.0.0 */ using json = basic_json<>; } @@ -6557,6 +7257,8 @@ namespace std { /*! @brief exchanges the values of two JSON objects + +@since version 1.0.0 */ template <> inline void swap(nlohmann::json& j1, @@ -6572,7 +7274,11 @@ inline void swap(nlohmann::json& j1, template <> struct hash { - /// return a hash value for a JSON object + /*! + @brief return a hash value for a JSON object + + @since version 1.0.0 + */ std::size_t operator()(const nlohmann::json& j) const { // a naive hashing via the string representation @@ -6591,11 +7297,12 @@ no parse error occurred. @param[in] s a string representation of a JSON object @return a JSON object + +@since version 1.0.0 */ inline nlohmann::json operator "" _json(const char* s, std::size_t) { - return nlohmann::json::parse(reinterpret_cast - (const_cast(s))); + return nlohmann::json::parse(reinterpret_cast(s)); } #endif diff --git a/test/catch.hpp b/test/catch.hpp index de61226cf..e2774f0be 100644 --- a/test/catch.hpp +++ b/test/catch.hpp @@ -1,6 +1,6 @@ /* - * Catch v1.2.1 - * Generated: 2015-06-30 18:23:27.961086 + * Catch v1.3.1 + * Generated: 2015-12-09 18:10:29.846134 * ---------------------------------------------------------- * This file has been merged from multiple headers. Please don't edit it directly * Copyright (c) 2012 Two Blue Cubes Ltd. All rights reserved. @@ -21,8 +21,6 @@ // #included from: internal/catch_suppress_warnings.h -#define TWOBLUECUBES_CATCH_SUPPRESS_WARNINGS_H_INCLUDED - #ifdef __clang__ # ifdef __ICC // icpc defines the __clang__ macro # pragma warning(push) @@ -37,6 +35,7 @@ # pragma clang diagnostic ignored "-Wc++98-compat" # pragma clang diagnostic ignored "-Wc++98-compat-pedantic" # pragma clang diagnostic ignored "-Wswitch-enum" +# pragma clang diagnostic ignored "-Wcovered-switch-default" # endif #elif defined __GNUC__ # pragma GCC diagnostic ignored "-Wvariadic-macros" @@ -44,7 +43,6 @@ # pragma GCC diagnostic push # pragma GCC diagnostic ignored "-Wpadded" #endif - #if defined(CATCH_CONFIG_MAIN) || defined(CATCH_CONFIG_RUNNER) # define CATCH_IMPL #endif @@ -84,11 +82,19 @@ // CATCH_CONFIG_CPP11_GENERATED_METHODS : The delete and default keywords for compiler generated methods // CATCH_CONFIG_CPP11_IS_ENUM : std::is_enum is supported? // CATCH_CONFIG_CPP11_TUPLE : std::tuple is supported +// CATCH_CONFIG_CPP11_LONG_LONG : is long long supported? +// CATCH_CONFIG_CPP11_OVERRIDE : is override supported? +// CATCH_CONFIG_CPP11_UNIQUE_PTR : is unique_ptr supported (otherwise use auto_ptr) // CATCH_CONFIG_CPP11_OR_GREATER : Is C++11 supported? // CATCH_CONFIG_VARIADIC_MACROS : are variadic macros supported? +// **************** +// Note to maintainers: if new toggles are added please document them +// in configuration.md, too +// **************** + // In general each macro has a _NO_ form // (e.g. CATCH_CONFIG_CPP11_NO_NULLPTR) which disables the feature. // Many features, at point of detection, define an _INTERNAL_ macro, so they @@ -130,10 +136,13 @@ // GCC #ifdef __GNUC__ -#if __GNUC__ > 4 || (__GNUC__ == 4 && __GNUC_MINOR__ >= 6 && defined(__GXX_EXPERIMENTAL_CXX0X__) ) +#if __GNUC__ == 4 && __GNUC_MINOR__ >= 6 && defined(__GXX_EXPERIMENTAL_CXX0X__) # define CATCH_INTERNAL_CONFIG_CPP11_NULLPTR #endif +// - otherwise more recent versions define __cplusplus >= 201103L +// and will get picked up below + #endif // __GNUC__ //////////////////////////////////////////////////////////////////////////////// @@ -142,6 +151,7 @@ #if (_MSC_VER >= 1600) # define CATCH_INTERNAL_CONFIG_CPP11_NULLPTR +# define CATCH_INTERNAL_CONFIG_CPP11_UNIQUE_PTR #endif #if (_MSC_VER >= 1900 ) // (VC++ 13 (VS2015)) @@ -151,6 +161,8 @@ #endif // _MSC_VER +//////////////////////////////////////////////////////////////////////////////// + // Use variadic macros if the compiler supports them #if ( defined _MSC_VER && _MSC_VER > 1400 && !defined __EDGE__) || \ ( defined __WAVE__ && __WAVE_HAS_VARIADICS ) || \ @@ -165,7 +177,7 @@ // C++ language feature support // catch all support for C++11 -#if (__cplusplus >= 201103L) +#if defined(__cplusplus) && __cplusplus >= 201103L # define CATCH_CPP11_OR_GREATER @@ -193,6 +205,17 @@ # define CATCH_INTERNAL_CONFIG_VARIADIC_MACROS # endif +# if !defined(CATCH_INTERNAL_CONFIG_CPP11_LONG_LONG) +# define CATCH_INTERNAL_CONFIG_CPP11_LONG_LONG +# endif + +# if !defined(CATCH_INTERNAL_CONFIG_CPP11_OVERRIDE) +# define CATCH_INTERNAL_CONFIG_CPP11_OVERRIDE +# endif +# if !defined(CATCH_INTERNAL_CONFIG_CPP11_UNIQUE_PTR) +# define CATCH_INTERNAL_CONFIG_CPP11_UNIQUE_PTR +# endif + #endif // __cplusplus >= 201103L // Now set the actual defines based on the above + anything the user has configured @@ -212,7 +235,16 @@ # define CATCH_CONFIG_CPP11_TUPLE #endif #if defined(CATCH_INTERNAL_CONFIG_VARIADIC_MACROS) && !defined(CATCH_CONFIG_NO_VARIADIC_MACROS) && !defined(CATCH_CONFIG_VARIADIC_MACROS) -#define CATCH_CONFIG_VARIADIC_MACROS +# define CATCH_CONFIG_VARIADIC_MACROS +#endif +#if defined(CATCH_INTERNAL_CONFIG_CPP11_LONG_LONG) && !defined(CATCH_CONFIG_NO_LONG_LONG) && !defined(CATCH_CONFIG_CPP11_LONG_LONG) && !defined(CATCH_CONFIG_NO_CPP11) +# define CATCH_CONFIG_CPP11_LONG_LONG +#endif +#if defined(CATCH_INTERNAL_CONFIG_CPP11_OVERRIDE) && !defined(CATCH_CONFIG_NO_OVERRIDE) && !defined(CATCH_CONFIG_CPP11_OVERRIDE) && !defined(CATCH_CONFIG_NO_CPP11) +# define CATCH_CONFIG_CPP11_OVERRIDE +#endif +#if defined(CATCH_INTERNAL_CONFIG_CPP11_UNIQUE_PTR) && !defined(CATCH_CONFIG_NO_UNIQUE_PTR) && !defined(CATCH_CONFIG_CPP11_UNIQUE_PTR) && !defined(CATCH_CONFIG_NO_CPP11) +# define CATCH_CONFIG_CPP11_UNIQUE_PTR #endif // noexcept support: @@ -224,8 +256,36 @@ # define CATCH_NOEXCEPT_IS(x) #endif +// nullptr support +#ifdef CATCH_CONFIG_CPP11_NULLPTR +# define CATCH_NULL nullptr +#else +# define CATCH_NULL NULL +#endif + +// override support +#ifdef CATCH_CONFIG_CPP11_OVERRIDE +# define CATCH_OVERRIDE override +#else +# define CATCH_OVERRIDE +#endif + +// unique_ptr support +#ifdef CATCH_CONFIG_CPP11_UNIQUE_PTR +# define CATCH_AUTO_PTR( T ) std::unique_ptr +#else +# define CATCH_AUTO_PTR( T ) std::auto_ptr +#endif + namespace Catch { + struct IConfig; + + struct CaseSensitive { enum Choice { + Yes, + No + }; }; + class NonCopyable { #ifdef CATCH_CONFIG_CPP11_GENERATED_METHODS NonCopyable( NonCopyable const& ) = delete; @@ -312,6 +372,9 @@ namespace Catch { void throwLogicError( std::string const& message, SourceLineInfo const& locationInfo ); + void seedRng( IConfig const& config ); + unsigned int rngSeed(); + // Use this in variadic streaming macros to allow // >> +StreamEndStop // as well as @@ -397,7 +460,7 @@ namespace Catch { template class Ptr { public: - Ptr() : m_p( NULL ){} + Ptr() : m_p( CATCH_NULL ){} Ptr( T* p ) : m_p( p ){ if( m_p ) m_p->addRef(); @@ -413,7 +476,7 @@ namespace Catch { void reset() { if( m_p ) m_p->release(); - m_p = NULL; + m_p = CATCH_NULL; } Ptr& operator = ( T* p ){ Ptr temp( p ); @@ -426,12 +489,11 @@ namespace Catch { return *this; } void swap( Ptr& other ) { std::swap( m_p, other.m_p ); } - T* get() { return m_p; } - const T* get() const{ return m_p; } + T* get() const{ return m_p; } T& operator*() const { return *m_p; } T* operator->() const { return m_p; } - bool operator !() const { return m_p == NULL; } - operator SafeBool::type() const { return SafeBool::makeSafe( m_p != NULL ); } + bool operator !() const { return m_p == CATCH_NULL; } + operator SafeBool::type() const { return SafeBool::makeSafe( m_p != CATCH_NULL ); } private: T* m_p; @@ -528,9 +590,13 @@ namespace Catch { struct ITestCaseRegistry { virtual ~ITestCaseRegistry(); virtual std::vector const& getAllTests() const = 0; - virtual void getFilteredTests( TestSpec const& testSpec, IConfig const& config, std::vector& matchingTestCases, bool negated = false ) const = 0; - + virtual std::vector const& getAllTestsSorted( IConfig const& config ) const = 0; }; + + bool matchTest( TestCase const& testCase, TestSpec const& testSpec, IConfig const& config ); + std::vector filterTests( std::vector const& testCases, TestSpec const& testSpec, IConfig const& config ); + std::vector const& getAllTestCasesSorted( IConfig const& config ); + } namespace Catch { @@ -563,27 +629,32 @@ struct NameAndDesc { const char* description; }; +void registerTestCase + ( ITestCase* testCase, + char const* className, + NameAndDesc const& nameAndDesc, + SourceLineInfo const& lineInfo ); + struct AutoReg { - AutoReg( TestFunction function, - SourceLineInfo const& lineInfo, - NameAndDesc const& nameAndDesc ); + AutoReg + ( TestFunction function, + SourceLineInfo const& lineInfo, + NameAndDesc const& nameAndDesc ); template - AutoReg( void (C::*method)(), - char const* className, - NameAndDesc const& nameAndDesc, - SourceLineInfo const& lineInfo ) { - registerTestCase( new MethodTestCase( method ), - className, - nameAndDesc, - lineInfo ); - } + AutoReg + ( void (C::*method)(), + char const* className, + NameAndDesc const& nameAndDesc, + SourceLineInfo const& lineInfo ) { - void registerTestCase( ITestCase* testCase, - char const* className, - NameAndDesc const& nameAndDesc, - SourceLineInfo const& lineInfo ); + registerTestCase + ( new MethodTestCase( method ), + className, + nameAndDesc, + lineInfo ); + } ~AutoReg(); @@ -592,6 +663,11 @@ private: void operator= ( AutoReg const& ); }; +void registerTestCaseFunction + ( TestFunction function, + SourceLineInfo const& lineInfo, + NameAndDesc const& nameAndDesc ); + } // end namespace Catch #ifdef CATCH_CONFIG_VARIADIC_MACROS @@ -615,6 +691,10 @@ private: } \ void INTERNAL_CATCH_UNIQUE_NAME( ____C_A_T_C_H____T_E_S_T____ )::test() + /////////////////////////////////////////////////////////////////////////////// + #define INTERNAL_CATCH_REGISTER_TESTCASE( Function, ... ) \ + Catch::AutoReg( Function, CATCH_INTERNAL_LINEINFO, Catch::NameAndDesc( __VA_ARGS__ ) ); + #else /////////////////////////////////////////////////////////////////////////////// #define INTERNAL_CATCH_TESTCASE( Name, Desc ) \ @@ -636,6 +716,9 @@ private: } \ void INTERNAL_CATCH_UNIQUE_NAME( ____C_A_T_C_H____T_E_S_T____ )::test() + /////////////////////////////////////////////////////////////////////////////// + #define INTERNAL_CATCH_REGISTER_TESTCASE( Function, Name, Desc ) \ + Catch::AutoReg( Function, CATCH_INTERNAL_LINEINFO, Catch::NameAndDesc( Name, Desc ) ); #endif // #included from: internal/catch_capture.hpp @@ -758,6 +841,323 @@ namespace Catch { } // end namespace Catch +// #included from: catch_matchers.hpp +#define TWOBLUECUBES_CATCH_MATCHERS_HPP_INCLUDED + +namespace Catch { +namespace Matchers { + namespace Impl { + + namespace Generic { + template class AllOf; + template class AnyOf; + template class Not; + } + + template + struct Matcher : SharedImpl + { + typedef ExpressionT ExpressionType; + + virtual ~Matcher() {} + virtual Ptr clone() const = 0; + virtual bool match( ExpressionT const& expr ) const = 0; + virtual std::string toString() const = 0; + + Generic::AllOf operator && ( Matcher const& other ) const; + Generic::AnyOf operator || ( Matcher const& other ) const; + Generic::Not operator ! () const; + }; + + template + struct MatcherImpl : Matcher { + + virtual Ptr > clone() const { + return Ptr >( new DerivedT( static_cast( *this ) ) ); + } + }; + + namespace Generic { + template + class Not : public MatcherImpl, ExpressionT> { + public: + explicit Not( Matcher const& matcher ) : m_matcher(matcher.clone()) {} + Not( Not const& other ) : m_matcher( other.m_matcher ) {} + + virtual bool match( ExpressionT const& expr ) const CATCH_OVERRIDE { + return !m_matcher->match( expr ); + } + + virtual std::string toString() const CATCH_OVERRIDE { + return "not " + m_matcher->toString(); + } + private: + Ptr< Matcher > m_matcher; + }; + + template + class AllOf : public MatcherImpl, ExpressionT> { + public: + + AllOf() {} + AllOf( AllOf const& other ) : m_matchers( other.m_matchers ) {} + + AllOf& add( Matcher const& matcher ) { + m_matchers.push_back( matcher.clone() ); + return *this; + } + virtual bool match( ExpressionT const& expr ) const + { + for( std::size_t i = 0; i < m_matchers.size(); ++i ) + if( !m_matchers[i]->match( expr ) ) + return false; + return true; + } + virtual std::string toString() const { + std::ostringstream oss; + oss << "( "; + for( std::size_t i = 0; i < m_matchers.size(); ++i ) { + if( i != 0 ) + oss << " and "; + oss << m_matchers[i]->toString(); + } + oss << " )"; + return oss.str(); + } + + AllOf operator && ( Matcher const& other ) const { + AllOf allOfExpr( *this ); + allOfExpr.add( other ); + return allOfExpr; + } + + private: + std::vector > > m_matchers; + }; + + template + class AnyOf : public MatcherImpl, ExpressionT> { + public: + + AnyOf() {} + AnyOf( AnyOf const& other ) : m_matchers( other.m_matchers ) {} + + AnyOf& add( Matcher const& matcher ) { + m_matchers.push_back( matcher.clone() ); + return *this; + } + virtual bool match( ExpressionT const& expr ) const + { + for( std::size_t i = 0; i < m_matchers.size(); ++i ) + if( m_matchers[i]->match( expr ) ) + return true; + return false; + } + virtual std::string toString() const { + std::ostringstream oss; + oss << "( "; + for( std::size_t i = 0; i < m_matchers.size(); ++i ) { + if( i != 0 ) + oss << " or "; + oss << m_matchers[i]->toString(); + } + oss << " )"; + return oss.str(); + } + + AnyOf operator || ( Matcher const& other ) const { + AnyOf anyOfExpr( *this ); + anyOfExpr.add( other ); + return anyOfExpr; + } + + private: + std::vector > > m_matchers; + }; + + } // namespace Generic + + template + Generic::AllOf Matcher::operator && ( Matcher const& other ) const { + Generic::AllOf allOfExpr; + allOfExpr.add( *this ); + allOfExpr.add( other ); + return allOfExpr; + } + + template + Generic::AnyOf Matcher::operator || ( Matcher const& other ) const { + Generic::AnyOf anyOfExpr; + anyOfExpr.add( *this ); + anyOfExpr.add( other ); + return anyOfExpr; + } + + template + Generic::Not Matcher::operator ! () const { + return Generic::Not( *this ); + } + + namespace StdString { + + inline std::string makeString( std::string const& str ) { return str; } + inline std::string makeString( const char* str ) { return str ? std::string( str ) : std::string(); } + + struct CasedString + { + CasedString( std::string const& str, CaseSensitive::Choice caseSensitivity ) + : m_caseSensitivity( caseSensitivity ), + m_str( adjustString( str ) ) + {} + std::string adjustString( std::string const& str ) const { + return m_caseSensitivity == CaseSensitive::No + ? toLower( str ) + : str; + + } + std::string toStringSuffix() const + { + return m_caseSensitivity == CaseSensitive::No + ? " (case insensitive)" + : ""; + } + CaseSensitive::Choice m_caseSensitivity; + std::string m_str; + }; + + struct Equals : MatcherImpl { + Equals( std::string const& str, CaseSensitive::Choice caseSensitivity = CaseSensitive::Yes ) + : m_data( str, caseSensitivity ) + {} + Equals( Equals const& other ) : m_data( other.m_data ){} + + virtual ~Equals(); + + virtual bool match( std::string const& expr ) const { + return m_data.m_str == m_data.adjustString( expr );; + } + virtual std::string toString() const { + return "equals: \"" + m_data.m_str + "\"" + m_data.toStringSuffix(); + } + + CasedString m_data; + }; + + struct Contains : MatcherImpl { + Contains( std::string const& substr, CaseSensitive::Choice caseSensitivity = CaseSensitive::Yes ) + : m_data( substr, caseSensitivity ){} + Contains( Contains const& other ) : m_data( other.m_data ){} + + virtual ~Contains(); + + virtual bool match( std::string const& expr ) const { + return m_data.adjustString( expr ).find( m_data.m_str ) != std::string::npos; + } + virtual std::string toString() const { + return "contains: \"" + m_data.m_str + "\"" + m_data.toStringSuffix(); + } + + CasedString m_data; + }; + + struct StartsWith : MatcherImpl { + StartsWith( std::string const& substr, CaseSensitive::Choice caseSensitivity = CaseSensitive::Yes ) + : m_data( substr, caseSensitivity ){} + + StartsWith( StartsWith const& other ) : m_data( other.m_data ){} + + virtual ~StartsWith(); + + virtual bool match( std::string const& expr ) const { + return m_data.adjustString( expr ).find( m_data.m_str ) == 0; + } + virtual std::string toString() const { + return "starts with: \"" + m_data.m_str + "\"" + m_data.toStringSuffix(); + } + + CasedString m_data; + }; + + struct EndsWith : MatcherImpl { + EndsWith( std::string const& substr, CaseSensitive::Choice caseSensitivity = CaseSensitive::Yes ) + : m_data( substr, caseSensitivity ){} + EndsWith( EndsWith const& other ) : m_data( other.m_data ){} + + virtual ~EndsWith(); + + virtual bool match( std::string const& expr ) const { + return m_data.adjustString( expr ).find( m_data.m_str ) == expr.size() - m_data.m_str.size(); + } + virtual std::string toString() const { + return "ends with: \"" + m_data.m_str + "\"" + m_data.toStringSuffix(); + } + + CasedString m_data; + }; + } // namespace StdString + } // namespace Impl + + // The following functions create the actual matcher objects. + // This allows the types to be inferred + template + inline Impl::Generic::Not Not( Impl::Matcher const& m ) { + return Impl::Generic::Not( m ); + } + + template + inline Impl::Generic::AllOf AllOf( Impl::Matcher const& m1, + Impl::Matcher const& m2 ) { + return Impl::Generic::AllOf().add( m1 ).add( m2 ); + } + template + inline Impl::Generic::AllOf AllOf( Impl::Matcher const& m1, + Impl::Matcher const& m2, + Impl::Matcher const& m3 ) { + return Impl::Generic::AllOf().add( m1 ).add( m2 ).add( m3 ); + } + template + inline Impl::Generic::AnyOf AnyOf( Impl::Matcher const& m1, + Impl::Matcher const& m2 ) { + return Impl::Generic::AnyOf().add( m1 ).add( m2 ); + } + template + inline Impl::Generic::AnyOf AnyOf( Impl::Matcher const& m1, + Impl::Matcher const& m2, + Impl::Matcher const& m3 ) { + return Impl::Generic::AnyOf().add( m1 ).add( m2 ).add( m3 ); + } + + inline Impl::StdString::Equals Equals( std::string const& str, CaseSensitive::Choice caseSensitivity = CaseSensitive::Yes ) { + return Impl::StdString::Equals( str, caseSensitivity ); + } + inline Impl::StdString::Equals Equals( const char* str, CaseSensitive::Choice caseSensitivity = CaseSensitive::Yes ) { + return Impl::StdString::Equals( Impl::StdString::makeString( str ), caseSensitivity ); + } + inline Impl::StdString::Contains Contains( std::string const& substr, CaseSensitive::Choice caseSensitivity = CaseSensitive::Yes ) { + return Impl::StdString::Contains( substr, caseSensitivity ); + } + inline Impl::StdString::Contains Contains( const char* substr, CaseSensitive::Choice caseSensitivity = CaseSensitive::Yes ) { + return Impl::StdString::Contains( Impl::StdString::makeString( substr ), caseSensitivity ); + } + inline Impl::StdString::StartsWith StartsWith( std::string const& substr ) { + return Impl::StdString::StartsWith( substr ); + } + inline Impl::StdString::StartsWith StartsWith( const char* substr ) { + return Impl::StdString::StartsWith( Impl::StdString::makeString( substr ) ); + } + inline Impl::StdString::EndsWith EndsWith( std::string const& substr ) { + return Impl::StdString::EndsWith( substr ); + } + inline Impl::StdString::EndsWith EndsWith( const char* substr ) { + return Impl::StdString::EndsWith( Impl::StdString::makeString( substr ) ); + } + +} // namespace Matchers + +using namespace Matchers; + +} // namespace Catch + namespace Catch { struct TestFailureException{}; @@ -784,7 +1184,8 @@ namespace Catch { ResultBuilder( char const* macroName, SourceLineInfo const& lineInfo, char const* capturedExpression, - ResultDisposition::Flags resultDisposition ); + ResultDisposition::Flags resultDisposition, + char const* secondArg = "" ); template ExpressionLhs operator <= ( T const& operand ); @@ -813,6 +1214,9 @@ namespace Catch { void useActiveException( ResultDisposition::Flags resultDisposition = ResultDisposition::Normal ); void captureResult( ResultWas::OfType resultType ); void captureExpression(); + void captureExpectedException( std::string const& expectedMessage ); + void captureExpectedException( Matchers::Impl::Matcher const& matcher ); + void handleResult( AssertionResult const& result ); void react(); bool shouldDebugBreak() const; bool allowThrows() const; @@ -991,13 +1395,51 @@ namespace Internal { return Evaluator::evaluate( lhs, reinterpret_cast( rhs ) ); } +#ifdef CATCH_CONFIG_CPP11_LONG_LONG + // long long to unsigned X + template bool compare( long long lhs, unsigned int rhs ) { + return applyEvaluator( static_cast( lhs ), rhs ); + } + template bool compare( long long lhs, unsigned long rhs ) { + return applyEvaluator( static_cast( lhs ), rhs ); + } + template bool compare( long long lhs, unsigned long long rhs ) { + return applyEvaluator( static_cast( lhs ), rhs ); + } + template bool compare( long long lhs, unsigned char rhs ) { + return applyEvaluator( static_cast( lhs ), rhs ); + } + + // unsigned long long to X + template bool compare( unsigned long long lhs, int rhs ) { + return applyEvaluator( static_cast( lhs ), rhs ); + } + template bool compare( unsigned long long lhs, long rhs ) { + return applyEvaluator( static_cast( lhs ), rhs ); + } + template bool compare( unsigned long long lhs, long long rhs ) { + return applyEvaluator( static_cast( lhs ), rhs ); + } + template bool compare( unsigned long long lhs, char rhs ) { + return applyEvaluator( static_cast( lhs ), rhs ); + } + + // pointer to long long (when comparing against NULL) + template bool compare( long long lhs, T* rhs ) { + return Evaluator::evaluate( reinterpret_cast( lhs ), rhs ); + } + template bool compare( T* lhs, long long rhs ) { + return Evaluator::evaluate( lhs, reinterpret_cast( rhs ) ); + } +#endif // CATCH_CONFIG_CPP11_LONG_LONG + #ifdef CATCH_CONFIG_CPP11_NULLPTR // pointer to nullptr_t (when comparing against nullptr) template bool compare( std::nullptr_t, T* rhs ) { - return Evaluator::evaluate( NULL, rhs ); + return Evaluator::evaluate( nullptr, rhs ); } template bool compare( T* lhs, std::nullptr_t ) { - return Evaluator::evaluate( lhs, NULL ); + return Evaluator::evaluate( lhs, nullptr ); } #endif // CATCH_CONFIG_CPP11_NULLPTR @@ -1095,6 +1537,11 @@ std::string toString( char value ); std::string toString( signed char value ); std::string toString( unsigned char value ); +#ifdef CATCH_CONFIG_CPP11_LONG_LONG +std::string toString( long long value ); +std::string toString( unsigned long long value ); +#endif + #ifdef CATCH_CONFIG_CPP11_NULLPTR std::string toString( std::nullptr_t ); #endif @@ -1107,7 +1554,7 @@ std::string toString( std::nullptr_t ); namespace Detail { - extern std::string unprintableString; + extern const std::string unprintableString; struct BorgType { template BorgType( T const& ); @@ -1190,7 +1637,7 @@ struct StringMaker { template static std::string convert( U* p ) { if( !p ) - return INTERNAL_CATCH_STRINGIFY( NULL ); + return "NULL"; else return Detail::rawMemoryToString( p ); } @@ -1200,7 +1647,7 @@ template struct StringMaker { static std::string convert( R C::* p ) { if( !p ) - return INTERNAL_CATCH_STRINGIFY( NULL ); + return "NULL"; else return Detail::rawMemoryToString( p ); } @@ -1472,6 +1919,7 @@ namespace Catch { class AssertionResult; struct AssertionInfo; struct SectionInfo; + struct SectionEndInfo; struct MessageInfo; class ScopedMessageBuilder; struct Counts; @@ -1483,7 +1931,8 @@ namespace Catch { virtual void assertionEnded( AssertionResult const& result ) = 0; virtual bool sectionStarted( SectionInfo const& sectionInfo, Counts& assertions ) = 0; - virtual void sectionEnded( SectionInfo const& name, Counts const& assertions, double _durationInSeconds ) = 0; + virtual void sectionEnded( SectionEndInfo const& endInfo ) = 0; + virtual void sectionEndedEarly( SectionEndInfo const& endInfo ) = 0; virtual void pushScopedMessage( MessageInfo const& message ) = 0; virtual void popScopedMessage( MessageInfo const& message ) = 0; @@ -1604,16 +2053,16 @@ namespace Catch { } while( Catch::alwaysFalse() ) /////////////////////////////////////////////////////////////////////////////// -#define INTERNAL_CATCH_THROWS( expr, resultDisposition, macroName ) \ +#define INTERNAL_CATCH_THROWS( expr, resultDisposition, matcher, macroName ) \ do { \ - Catch::ResultBuilder __catchResult( macroName, CATCH_INTERNAL_LINEINFO, #expr, resultDisposition ); \ + Catch::ResultBuilder __catchResult( macroName, CATCH_INTERNAL_LINEINFO, #expr, resultDisposition, #matcher ); \ if( __catchResult.allowThrows() ) \ try { \ expr; \ __catchResult.captureResult( Catch::ResultWas::DidntThrowException ); \ } \ catch( ... ) { \ - __catchResult.captureResult( Catch::ResultWas::Ok ); \ + __catchResult.captureExpectedException( matcher ); \ } \ else \ __catchResult.captureResult( Catch::ResultWas::Ok ); \ @@ -1668,12 +2117,12 @@ namespace Catch { do { \ Catch::ResultBuilder __catchResult( macroName, CATCH_INTERNAL_LINEINFO, #arg " " #matcher, resultDisposition ); \ try { \ - std::string matcherAsString = ::Catch::Matchers::matcher.toString(); \ + std::string matcherAsString = (matcher).toString(); \ __catchResult \ .setLhs( Catch::toString( arg ) ) \ .setRhs( matcherAsString == Catch::Detail::unprintableString ? #matcher : matcherAsString ) \ .setOp( "matches" ) \ - .setResultType( ::Catch::Matchers::matcher.match( arg ) ); \ + .setResultType( (matcher).match( arg ) ); \ __catchResult.captureExpression(); \ } catch( ... ) { \ __catchResult.useActiveException( resultDisposition | Catch::ResultDisposition::ContinueOnFailure ); \ @@ -1687,21 +2136,6 @@ namespace Catch { // #included from: catch_section_info.h #define TWOBLUECUBES_CATCH_SECTION_INFO_H_INCLUDED -namespace Catch { - - struct SectionInfo { - SectionInfo - ( SourceLineInfo const& _lineInfo, - std::string const& _name, - std::string const& _description = std::string() ); - - std::string name; - std::string description; - SourceLineInfo lineInfo; - }; - -} // end namespace Catch - // #included from: catch_totals.hpp #define TWOBLUECUBES_CATCH_TOTALS_HPP_INCLUDED @@ -1772,6 +2206,31 @@ namespace Catch { }; } +namespace Catch { + + struct SectionInfo { + SectionInfo + ( SourceLineInfo const& _lineInfo, + std::string const& _name, + std::string const& _description = std::string() ); + + std::string name; + std::string description; + SourceLineInfo lineInfo; + }; + + struct SectionEndInfo { + SectionEndInfo( SectionInfo const& _sectionInfo, Counts const& _prevAssertions, double _durationInSeconds ) + : sectionInfo( _sectionInfo ), prevAssertions( _prevAssertions ), durationInSeconds( _durationInSeconds ) + {} + + SectionInfo sectionInfo; + Counts prevAssertions; + double durationInSeconds; + }; + +} // end namespace Catch + // #included from: catch_timer.h #define TWOBLUECUBES_CATCH_TIMER_H_INCLUDED @@ -2012,6 +2471,8 @@ using namespace Generators; #define TWOBLUECUBES_CATCH_INTERFACES_EXCEPTION_H_INCLUDED #include +#include + // #included from: catch_interfaces_registry_hub.h #define TWOBLUECUBES_CATCH_INTERFACES_REGISTRY_HUB_H_INCLUDED @@ -2036,7 +2497,8 @@ namespace Catch { struct IMutableRegistryHub { virtual ~IMutableRegistryHub(); - virtual void registerReporter( std::string const& name, IReporterFactory* factory ) = 0; + virtual void registerReporter( std::string const& name, Ptr const& factory ) = 0; + virtual void registerListener( Ptr const& factory ) = 0; virtual void registerTest( TestCase const& testInfo ) = 0; virtual void registerTranslator( const IExceptionTranslator* translator ) = 0; }; @@ -2048,14 +2510,16 @@ namespace Catch { } - namespace Catch { typedef std::string(*exceptionTranslateFunction)(); + struct IExceptionTranslator; + typedef std::vector ExceptionTranslators; + struct IExceptionTranslator { virtual ~IExceptionTranslator(); - virtual std::string translate() const = 0; + virtual std::string translate( ExceptionTranslators::const_iterator it, ExceptionTranslators::const_iterator itEnd ) const = 0; }; struct IExceptionTranslatorRegistry { @@ -2073,9 +2537,12 @@ namespace Catch { : m_translateFunction( translateFunction ) {} - virtual std::string translate() const { + virtual std::string translate( ExceptionTranslators::const_iterator it, ExceptionTranslators::const_iterator itEnd ) const CATCH_OVERRIDE { try { - throw; + if( it == itEnd ) + throw; + else + return (*it)->translate( it+1, itEnd ); } catch( T& ex ) { return m_translateFunction( ex ); @@ -2182,231 +2649,6 @@ inline std::string toString( Detail::Approx const& value ) { } // end namespace Catch -// #included from: internal/catch_matchers.hpp -#define TWOBLUECUBES_CATCH_MATCHERS_HPP_INCLUDED - -namespace Catch { -namespace Matchers { - namespace Impl { - - template - struct Matcher : SharedImpl - { - typedef ExpressionT ExpressionType; - - virtual ~Matcher() {} - virtual Ptr clone() const = 0; - virtual bool match( ExpressionT const& expr ) const = 0; - virtual std::string toString() const = 0; - }; - - template - struct MatcherImpl : Matcher { - - virtual Ptr > clone() const { - return Ptr >( new DerivedT( static_cast( *this ) ) ); - } - }; - - namespace Generic { - - template - class AllOf : public MatcherImpl, ExpressionT> { - public: - - AllOf() {} - AllOf( AllOf const& other ) : m_matchers( other.m_matchers ) {} - - AllOf& add( Matcher const& matcher ) { - m_matchers.push_back( matcher.clone() ); - return *this; - } - virtual bool match( ExpressionT const& expr ) const - { - for( std::size_t i = 0; i < m_matchers.size(); ++i ) - if( !m_matchers[i]->match( expr ) ) - return false; - return true; - } - virtual std::string toString() const { - std::ostringstream oss; - oss << "( "; - for( std::size_t i = 0; i < m_matchers.size(); ++i ) { - if( i != 0 ) - oss << " and "; - oss << m_matchers[i]->toString(); - } - oss << " )"; - return oss.str(); - } - - private: - std::vector > > m_matchers; - }; - - template - class AnyOf : public MatcherImpl, ExpressionT> { - public: - - AnyOf() {} - AnyOf( AnyOf const& other ) : m_matchers( other.m_matchers ) {} - - AnyOf& add( Matcher const& matcher ) { - m_matchers.push_back( matcher.clone() ); - return *this; - } - virtual bool match( ExpressionT const& expr ) const - { - for( std::size_t i = 0; i < m_matchers.size(); ++i ) - if( m_matchers[i]->match( expr ) ) - return true; - return false; - } - virtual std::string toString() const { - std::ostringstream oss; - oss << "( "; - for( std::size_t i = 0; i < m_matchers.size(); ++i ) { - if( i != 0 ) - oss << " or "; - oss << m_matchers[i]->toString(); - } - oss << " )"; - return oss.str(); - } - - private: - std::vector > > m_matchers; - }; - - } - - namespace StdString { - - inline std::string makeString( std::string const& str ) { return str; } - inline std::string makeString( const char* str ) { return str ? std::string( str ) : std::string(); } - - struct Equals : MatcherImpl { - Equals( std::string const& str ) : m_str( str ){} - Equals( Equals const& other ) : m_str( other.m_str ){} - - virtual ~Equals(); - - virtual bool match( std::string const& expr ) const { - return m_str == expr; - } - virtual std::string toString() const { - return "equals: \"" + m_str + "\""; - } - - std::string m_str; - }; - - struct Contains : MatcherImpl { - Contains( std::string const& substr ) : m_substr( substr ){} - Contains( Contains const& other ) : m_substr( other.m_substr ){} - - virtual ~Contains(); - - virtual bool match( std::string const& expr ) const { - return expr.find( m_substr ) != std::string::npos; - } - virtual std::string toString() const { - return "contains: \"" + m_substr + "\""; - } - - std::string m_substr; - }; - - struct StartsWith : MatcherImpl { - StartsWith( std::string const& substr ) : m_substr( substr ){} - StartsWith( StartsWith const& other ) : m_substr( other.m_substr ){} - - virtual ~StartsWith(); - - virtual bool match( std::string const& expr ) const { - return expr.find( m_substr ) == 0; - } - virtual std::string toString() const { - return "starts with: \"" + m_substr + "\""; - } - - std::string m_substr; - }; - - struct EndsWith : MatcherImpl { - EndsWith( std::string const& substr ) : m_substr( substr ){} - EndsWith( EndsWith const& other ) : m_substr( other.m_substr ){} - - virtual ~EndsWith(); - - virtual bool match( std::string const& expr ) const { - return expr.find( m_substr ) == expr.size() - m_substr.size(); - } - virtual std::string toString() const { - return "ends with: \"" + m_substr + "\""; - } - - std::string m_substr; - }; - } // namespace StdString - } // namespace Impl - - // The following functions create the actual matcher objects. - // This allows the types to be inferred - template - inline Impl::Generic::AllOf AllOf( Impl::Matcher const& m1, - Impl::Matcher const& m2 ) { - return Impl::Generic::AllOf().add( m1 ).add( m2 ); - } - template - inline Impl::Generic::AllOf AllOf( Impl::Matcher const& m1, - Impl::Matcher const& m2, - Impl::Matcher const& m3 ) { - return Impl::Generic::AllOf().add( m1 ).add( m2 ).add( m3 ); - } - template - inline Impl::Generic::AnyOf AnyOf( Impl::Matcher const& m1, - Impl::Matcher const& m2 ) { - return Impl::Generic::AnyOf().add( m1 ).add( m2 ); - } - template - inline Impl::Generic::AnyOf AnyOf( Impl::Matcher const& m1, - Impl::Matcher const& m2, - Impl::Matcher const& m3 ) { - return Impl::Generic::AnyOf().add( m1 ).add( m2 ).add( m3 ); - } - - inline Impl::StdString::Equals Equals( std::string const& str ) { - return Impl::StdString::Equals( str ); - } - inline Impl::StdString::Equals Equals( const char* str ) { - return Impl::StdString::Equals( Impl::StdString::makeString( str ) ); - } - inline Impl::StdString::Contains Contains( std::string const& substr ) { - return Impl::StdString::Contains( substr ); - } - inline Impl::StdString::Contains Contains( const char* substr ) { - return Impl::StdString::Contains( Impl::StdString::makeString( substr ) ); - } - inline Impl::StdString::StartsWith StartsWith( std::string const& substr ) { - return Impl::StdString::StartsWith( substr ); - } - inline Impl::StdString::StartsWith StartsWith( const char* substr ) { - return Impl::StdString::StartsWith( Impl::StdString::makeString( substr ) ); - } - inline Impl::StdString::EndsWith EndsWith( std::string const& substr ) { - return Impl::StdString::EndsWith( substr ); - } - inline Impl::StdString::EndsWith EndsWith( const char* substr ) { - return Impl::StdString::EndsWith( Impl::StdString::makeString( substr ) ); - } - -} // namespace Matchers - -using namespace Matchers; - -} // namespace Catch - // #included from: internal/catch_interfaces_tag_alias_registry.h #define TWOBLUECUBES_CATCH_INTERFACES_TAG_ALIAS_REGISTRY_H_INCLUDED @@ -2440,12 +2682,12 @@ namespace Catch { template class Option { public: - Option() : nullableValue( NULL ) {} + Option() : nullableValue( CATCH_NULL ) {} Option( T const& _value ) : nullableValue( new( storage ) T( _value ) ) {} Option( Option const& _other ) - : nullableValue( _other ? new( storage ) T( *_other ) : NULL ) + : nullableValue( _other ? new( storage ) T( *_other ) : CATCH_NULL ) {} ~Option() { @@ -2469,7 +2711,7 @@ namespace Catch { void reset() { if( nullableValue ) nullableValue->~T(); - nullableValue = NULL; + nullableValue = CATCH_NULL; } T& operator*() { return *nullableValue; } @@ -2481,10 +2723,10 @@ namespace Catch { return nullableValue ? *nullableValue : defaultValue; } - bool some() const { return nullableValue != NULL; } - bool none() const { return nullableValue == NULL; } + bool some() const { return nullableValue != CATCH_NULL; } + bool none() const { return nullableValue == CATCH_NULL; } - bool operator !() const { return nullableValue == NULL; } + bool operator !() const { return nullableValue == CATCH_NULL; } operator SafeBool::type() const { return SafeBool::makeSafe( some() ); } @@ -2542,6 +2784,8 @@ namespace Catch { TestCaseInfo( TestCaseInfo const& other ); + friend void setTags( TestCaseInfo& testCaseInfo, std::set const& tags ); + bool isHidden() const; bool throws() const; bool okToFail() const; @@ -2654,7 +2898,7 @@ namespace Catch { inline size_t registerTestMethods() { size_t noTestMethods = 0; - int noClasses = objc_getClassList( NULL, 0 ); + int noClasses = objc_getClassList( CATCH_NULL, 0 ); Class* classes = (CATCH_UNSAFE_UNRETAINED Class *)malloc( sizeof(Class) * noClasses); objc_getClassList( classes, noClasses ); @@ -2796,7 +3040,7 @@ return @ desc; \ #pragma clang diagnostic ignored "-Wweak-vtables" #endif -// #included from: ../catch_runner.hpp +// #included from: ../catch_session.hpp #define TWOBLUECUBES_CATCH_RUNNER_HPP_INCLUDED // #included from: internal/catch_commandline.hpp @@ -2821,6 +3065,67 @@ return @ desc; \ #pragma clang diagnostic ignored "-Wpadded" #endif +// #included from: catch_wildcard_pattern.hpp +#define TWOBLUECUBES_CATCH_WILDCARD_PATTERN_HPP_INCLUDED + +namespace Catch +{ + class WildcardPattern { + enum WildcardPosition { + NoWildcard = 0, + WildcardAtStart = 1, + WildcardAtEnd = 2, + WildcardAtBothEnds = WildcardAtStart | WildcardAtEnd + }; + + public: + + WildcardPattern( std::string const& pattern, CaseSensitive::Choice caseSensitivity ) + : m_caseSensitivity( caseSensitivity ), + m_wildcard( NoWildcard ), + m_pattern( adjustCase( pattern ) ) + { + if( startsWith( m_pattern, "*" ) ) { + m_pattern = m_pattern.substr( 1 ); + m_wildcard = WildcardAtStart; + } + if( endsWith( m_pattern, "*" ) ) { + m_pattern = m_pattern.substr( 0, m_pattern.size()-1 ); + m_wildcard = static_cast( m_wildcard | WildcardAtEnd ); + } + } + virtual ~WildcardPattern(); + virtual bool matches( std::string const& str ) const { + switch( m_wildcard ) { + case NoWildcard: + return m_pattern == adjustCase( str ); + case WildcardAtStart: + return endsWith( adjustCase( str ), m_pattern ); + case WildcardAtEnd: + return startsWith( adjustCase( str ), m_pattern ); + case WildcardAtBothEnds: + return contains( adjustCase( str ), m_pattern ); + } + +#ifdef __clang__ +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wunreachable-code" +#endif + throw std::logic_error( "Unknown enum" ); +#ifdef __clang__ +#pragma clang diagnostic pop +#endif + } + private: + std::string adjustCase( std::string const& str ) const { + return m_caseSensitivity == CaseSensitive::No ? toLower( str ) : str; + } + CaseSensitive::Choice m_caseSensitivity; + WildcardPosition m_wildcard; + std::string m_pattern; + }; +} + #include #include @@ -2832,50 +3137,18 @@ namespace Catch { virtual bool matches( TestCaseInfo const& testCase ) const = 0; }; class NamePattern : public Pattern { - enum WildcardPosition { - NoWildcard = 0, - WildcardAtStart = 1, - WildcardAtEnd = 2, - WildcardAtBothEnds = WildcardAtStart | WildcardAtEnd - }; - public: - NamePattern( std::string const& name ) : m_name( toLower( name ) ), m_wildcard( NoWildcard ) { - if( startsWith( m_name, "*" ) ) { - m_name = m_name.substr( 1 ); - m_wildcard = WildcardAtStart; - } - if( endsWith( m_name, "*" ) ) { - m_name = m_name.substr( 0, m_name.size()-1 ); - m_wildcard = static_cast( m_wildcard | WildcardAtEnd ); - } - } + NamePattern( std::string const& name ) + : m_wildcardPattern( toLower( name ), CaseSensitive::No ) + {} virtual ~NamePattern(); virtual bool matches( TestCaseInfo const& testCase ) const { - switch( m_wildcard ) { - case NoWildcard: - return m_name == toLower( testCase.name ); - case WildcardAtStart: - return endsWith( toLower( testCase.name ), m_name ); - case WildcardAtEnd: - return startsWith( toLower( testCase.name ), m_name ); - case WildcardAtBothEnds: - return contains( toLower( testCase.name ), m_name ); - } - -#ifdef __clang__ -#pragma clang diagnostic push -#pragma clang diagnostic ignored "-Wunreachable-code" -#endif - throw std::logic_error( "Unknown enum" ); -#ifdef __clang__ -#pragma clang diagnostic pop -#endif + return m_wildcardPattern.matches( toLower( testCase.name ) ); } private: - std::string m_name; - WildcardPosition m_wildcard; + WildcardPattern m_wildcardPattern; }; + class TagPattern : public Pattern { public: TagPattern( std::string const& tag ) : m_tag( toLower( tag ) ) {} @@ -2886,6 +3159,7 @@ namespace Catch { private: std::string m_tag; }; + class ExcludedPattern : public Pattern { public: ExcludedPattern( Ptr const& underlyingPattern ) : m_underlyingPattern( underlyingPattern ) {} @@ -3083,28 +3357,62 @@ namespace Catch { // #included from: catch_stream.h #define TWOBLUECUBES_CATCH_STREAM_H_INCLUDED -#include +// #included from: catch_streambuf.h +#define TWOBLUECUBES_CATCH_STREAMBUF_H_INCLUDED -#ifdef __clang__ -#pragma clang diagnostic ignored "-Wpadded" -#endif +#include namespace Catch { - class Stream { + class StreamBufBase : public std::streambuf { public: - Stream(); - Stream( std::streambuf* _streamBuf, bool _isOwned ); - void release(); - - std::streambuf* streamBuf; - - private: - bool isOwned; + virtual ~StreamBufBase() CATCH_NOEXCEPT; }; +} + +#include +#include +#include + +namespace Catch { std::ostream& cout(); std::ostream& cerr(); + + struct IStream { + virtual ~IStream() CATCH_NOEXCEPT; + virtual std::ostream& stream() const = 0; + }; + + class FileStream : public IStream { + mutable std::ofstream m_ofs; + public: + FileStream( std::string const& filename ); + virtual ~FileStream() CATCH_NOEXCEPT; + public: // IStream + virtual std::ostream& stream() const CATCH_OVERRIDE; + }; + + class CoutStream : public IStream { + mutable std::ostream m_os; + public: + CoutStream(); + virtual ~CoutStream() CATCH_NOEXCEPT; + + public: // IStream + virtual std::ostream& stream() const CATCH_OVERRIDE; + }; + + class DebugOutStream : public IStream { + std::auto_ptr m_streamBuf; + mutable std::ostream m_os; + public: + DebugOutStream(); + virtual ~DebugOutStream() CATCH_NOEXCEPT; + + public: // IStream + virtual std::ostream& stream() const CATCH_OVERRIDE; + }; } #include @@ -3132,6 +3440,7 @@ namespace Catch { showHelp( false ), showInvisibles( false ), forceColour( false ), + filenamesAsTags( false ), abortAfter( -1 ), rngSeed( 0 ), verbosity( Verbosity::Normal ), @@ -3151,6 +3460,7 @@ namespace Catch { bool showHelp; bool showInvisibles; bool forceColour; + bool filenamesAsTags; int abortAfter; unsigned int rngSeed; @@ -3160,11 +3470,11 @@ namespace Catch { ShowDurations::OrNot showDurations; RunTests::InWhatOrder runOrder; - std::string reporterName; std::string outputFilename; std::string name; std::string processName; + std::vector reporterNames; std::vector testsOrTags; }; @@ -3176,12 +3486,11 @@ namespace Catch { public: Config() - : m_os( Catch::cout().rdbuf() ) {} Config( ConfigData const& data ) : m_data( data ), - m_os( Catch::cout().rdbuf() ) + m_stream( openStream() ) { if( !data.testsOrTags.empty() ) { TestSpecParser parser( ITagAliasRegistry::get() ); @@ -3192,12 +3501,6 @@ namespace Catch { } virtual ~Config() { - m_os.rdbuf( Catch::cout().rdbuf() ); - m_stream.release(); - } - - void setFilename( std::string const& filename ) { - m_data.outputFilename = filename; } std::string const& getFilename() const { @@ -3213,18 +3516,7 @@ namespace Catch { bool shouldDebugBreak() const { return m_data.shouldDebugBreak; } - void setStreamBuf( std::streambuf* buf ) { - m_os.rdbuf( buf ? buf : Catch::cout().rdbuf() ); - } - - void useStream( std::string const& streamName ) { - Stream stream = createStream( streamName ); - setStreamBuf( stream.streamBuf ); - m_stream.release(); - m_stream = stream; - } - - std::string getReporterName() const { return m_data.reporterName; } + std::vector getReporterNames() const { return m_data.reporterNames; } int abortAfter() const { return m_data.abortAfter; } @@ -3235,7 +3527,7 @@ namespace Catch { // IConfig interface virtual bool allowThrows() const { return !m_data.noThrow; } - virtual std::ostream& stream() const { return m_os; } + virtual std::ostream& stream() const { return m_stream->stream(); } virtual std::string name() const { return m_data.name.empty() ? m_data.processName : m_data.name; } virtual bool includeSuccessfulResults() const { return m_data.showSuccessfulTests; } virtual bool warnAboutMissingAssertions() const { return m_data.warnings & WarnAbout::NoAssertions; } @@ -3245,10 +3537,22 @@ namespace Catch { virtual bool forceColour() const { return m_data.forceColour; } private: + + IStream const* openStream() { + if( m_data.outputFilename.empty() ) + return new CoutStream(); + else if( m_data.outputFilename[0] == '%' ) { + if( m_data.outputFilename == "%debug" ) + return new DebugOutStream(); + else + throw std::domain_error( "Unrecognised stream: " + m_data.outputFilename ); + } + else + return new FileStream( m_data.outputFilename ); + } ConfigData m_data; - Stream m_stream; - mutable std::ostream m_os; + std::auto_ptr m_stream; TestSpec m_testSpec; }; @@ -3517,11 +3821,11 @@ namespace Clara { template class BoundArgFunction { public: - BoundArgFunction() : functionObj( NULL ) {} + BoundArgFunction() : functionObj( CATCH_NULL ) {} BoundArgFunction( IArgFunction* _functionObj ) : functionObj( _functionObj ) {} - BoundArgFunction( BoundArgFunction const& other ) : functionObj( other.functionObj ? other.functionObj->clone() : NULL ) {} + BoundArgFunction( BoundArgFunction const& other ) : functionObj( other.functionObj ? other.functionObj->clone() : CATCH_NULL ) {} BoundArgFunction& operator = ( BoundArgFunction const& other ) { - IArgFunction* newFunctionObj = other.functionObj ? other.functionObj->clone() : NULL; + IArgFunction* newFunctionObj = other.functionObj ? other.functionObj->clone() : CATCH_NULL; delete functionObj; functionObj = newFunctionObj; return *this; @@ -3537,7 +3841,7 @@ namespace Clara { bool takesArg() const { return functionObj->takesArg(); } bool isSet() const { - return functionObj != NULL; + return functionObj != CATCH_NULL; } private: IArgFunction* functionObj; @@ -3755,12 +4059,7 @@ namespace Clara { } }; - // NOTE: std::auto_ptr is deprecated in c++11/c++0x -#if defined(__cplusplus) && __cplusplus > 199711L - typedef std::unique_ptr ArgAutoPtr; -#else - typedef std::auto_ptr ArgAutoPtr; -#endif + typedef CATCH_AUTO_PTR( Arg ) ArgAutoPtr; friend void addOptName( Arg& arg, std::string const& optName ) { @@ -3836,8 +4135,8 @@ namespace Clara { m_arg->description = description; return *this; } - ArgBuilder& detail( std::string const& detail ) { - m_arg->detail = detail; + ArgBuilder& detail( std::string const& _detail ) { + m_arg->detail = _detail; return *this; } @@ -3920,14 +4219,14 @@ namespace Clara { maxWidth = (std::max)( maxWidth, it->commands().size() ); for( it = itBegin; it != itEnd; ++it ) { - Detail::Text usage( it->commands(), Detail::TextAttributes() + Detail::Text usageText( it->commands(), Detail::TextAttributes() .setWidth( maxWidth+indent ) .setIndent( indent ) ); Detail::Text desc( it->description, Detail::TextAttributes() .setWidth( width - maxWidth - 3 ) ); - for( std::size_t i = 0; i < (std::max)( usage.size(), desc.size() ); ++i ) { - std::string usageCol = i < usage.size() ? usage[i] : ""; + for( std::size_t i = 0; i < (std::max)( usageText.size(), desc.size() ); ++i ) { + std::string usageCol = i < usageText.size() ? usageText[i] : ""; os << usageCol; if( i < desc.size() && !desc[i].empty() ) @@ -4133,6 +4432,7 @@ namespace Catch { config.abortAfter = x; } inline void addTestOrTags( ConfigData& config, std::string const& _testSpec ) { config.testsOrTags.push_back( _testSpec ); } + inline void addReporterName( ConfigData& config, std::string const& _reporterName ) { config.reporterNames.push_back( _reporterName ); } inline void addWarning( ConfigData& config, std::string const& _warning ) { if( _warning == "NoAssertions" ) @@ -4226,7 +4526,7 @@ namespace Catch { cli["-r"]["--reporter"] // .placeholder( "name[:filename]" ) .describe( "reporter to use (defaults to console)" ) - .bind( &ConfigData::reporterName, "name" ); + .bind( &addReporterName, "name" ); cli["-n"]["--name"] .describe( "suite name" ) @@ -4263,6 +4563,10 @@ namespace Catch { .describe( "load test names to run from a file" ) .bind( &loadTestNamesFromFile, "filename" ); + cli["-#"]["--filenames-as-tags"] + .describe( "adds a tag for the filename" ) + .bind( &ConfigData::filenamesAsTags ); + // Less common commands which don't have a short form cli["--list-test-names-only"] .describe( "list all/matching test cases names only" ) @@ -4520,18 +4824,18 @@ namespace Catch { namespace Catch { struct ReporterConfig { - explicit ReporterConfig( Ptr const& _fullConfig ) + explicit ReporterConfig( Ptr const& _fullConfig ) : m_stream( &_fullConfig->stream() ), m_fullConfig( _fullConfig ) {} - ReporterConfig( Ptr const& _fullConfig, std::ostream& _stream ) + ReporterConfig( Ptr const& _fullConfig, std::ostream& _stream ) : m_stream( &_stream ), m_fullConfig( _fullConfig ) {} std::ostream& stream() const { return *m_stream; } - Ptr fullConfig() const { return m_fullConfig; } + Ptr fullConfig() const { return m_fullConfig; } private: std::ostream* m_stream; - Ptr m_fullConfig; + Ptr m_fullConfig; }; struct ReporterPreferences { @@ -4733,6 +5037,7 @@ namespace Catch // The return value indicates if the messages buffer should be cleared: virtual bool assertionEnded( AssertionStats const& assertionStats ) = 0; + virtual void sectionEnded( SectionStats const& sectionStats ) = 0; virtual void testCaseEnded( TestCaseStats const& testCaseStats ) = 0; virtual void testGroupEnded( TestGroupStats const& testGroupStats ) = 0; @@ -4741,20 +5046,24 @@ namespace Catch virtual void skipTest( TestCaseInfo const& testInfo ) = 0; }; - struct IReporterFactory { + struct IReporterFactory : IShared { virtual ~IReporterFactory(); virtual IStreamingReporter* create( ReporterConfig const& config ) const = 0; virtual std::string getDescription() const = 0; }; struct IReporterRegistry { - typedef std::map FactoryMap; + typedef std::map > FactoryMap; + typedef std::vector > Listeners; virtual ~IReporterRegistry(); - virtual IStreamingReporter* create( std::string const& name, Ptr const& config ) const = 0; + virtual IStreamingReporter* create( std::string const& name, Ptr const& config ) const = 0; virtual FactoryMap const& getFactories() const = 0; + virtual Listeners const& getListeners() const = 0; }; + Ptr addReporter( Ptr const& existingReporter, Ptr const& additionalReporter ); + } #include @@ -4777,8 +5086,7 @@ namespace Catch { nameAttr.setInitialIndent( 2 ).setIndent( 4 ); tagsAttr.setIndent( 6 ); - std::vector matchedTestCases; - getRegistryHub().getTestCaseRegistry().getFilteredTests( testSpec, config, matchedTestCases ); + std::vector matchedTestCases = filterTests( getAllTestCasesSorted( config ), testSpec, config ); for( std::vector::const_iterator it = matchedTestCases.begin(), itEnd = matchedTestCases.end(); it != itEnd; ++it ) { @@ -4806,8 +5114,7 @@ namespace Catch { if( !config.testSpec().hasFilters() ) testSpec = TestSpecParser( ITagAliasRegistry::get() ).parse( "*" ).testSpec(); std::size_t matchedTests = 0; - std::vector matchedTestCases; - getRegistryHub().getTestCaseRegistry().getFilteredTests( testSpec, config, matchedTestCases ); + std::vector matchedTestCases = filterTests( getAllTestCasesSorted( config ), testSpec, config ); for( std::vector::const_iterator it = matchedTestCases.begin(), itEnd = matchedTestCases.end(); it != itEnd; ++it ) { @@ -4847,8 +5154,7 @@ namespace Catch { std::map tagCounts; - std::vector matchedTestCases; - getRegistryHub().getTestCaseRegistry().getFilteredTests( testSpec, config, matchedTestCases ); + std::vector matchedTestCases = filterTests( getAllTestCasesSorted( config ), testSpec, config ); for( std::vector::const_iterator it = matchedTestCases.begin(), itEnd = matchedTestCases.end(); it != itEnd; ++it ) { @@ -4919,7 +5225,7 @@ namespace Catch { } // end namespace Catch -// #included from: internal/catch_runner_impl.hpp +// #included from: internal/catch_run_context.hpp #define TWOBLUECUBES_CATCH_RUNNER_IMPL_HPP_INCLUDED // #included from: catch_test_case_tracker.hpp @@ -4928,137 +5234,300 @@ namespace Catch { #include #include #include +#include namespace Catch { -namespace SectionTracking { +namespace TestCaseTracking { - class TrackedSection { + struct ITracker : SharedImpl<> { + virtual ~ITracker(); - typedef std::map TrackedSections; + // static queries + virtual std::string name() const = 0; + + // dynamic queries + virtual bool isComplete() const = 0; // Successfully completed or failed + virtual bool isSuccessfullyCompleted() const = 0; + virtual bool isOpen() const = 0; // Started but not complete + virtual bool hasChildren() const = 0; + + virtual ITracker& parent() = 0; + + // actions + virtual void close() = 0; // Successfully complete + virtual void fail() = 0; + virtual void markAsNeedingAnotherRun() = 0; + + virtual void addChild( Ptr const& child ) = 0; + virtual ITracker* findChild( std::string const& name ) = 0; + virtual void openChild() = 0; + }; + + class TrackerContext { - public: enum RunState { NotStarted, Executing, - ExecutingChildren, - Completed + CompletedCycle }; - TrackedSection( std::string const& name, TrackedSection* parent ) - : m_name( name ), m_runState( NotStarted ), m_parent( parent ) + Ptr m_rootTracker; + ITracker* m_currentTracker; + RunState m_runState; + + public: + + static TrackerContext& instance() { + static TrackerContext s_instance; + return s_instance; + } + + TrackerContext() + : m_currentTracker( CATCH_NULL ), + m_runState( NotStarted ) {} - RunState runState() const { return m_runState; } + ITracker& startRun(); - TrackedSection* findChild( std::string const& childName ); - TrackedSection* acquireChild( std::string const& childName ); - - void enter() { - if( m_runState == NotStarted ) - m_runState = Executing; + void endRun() { + m_rootTracker.reset(); + m_currentTracker = CATCH_NULL; + m_runState = NotStarted; } - void leave(); - TrackedSection* getParent() { - return m_parent; + void startCycle() { + m_currentTracker = m_rootTracker.get(); + m_runState = Executing; } - bool hasChildren() const { + void completeCycle() { + m_runState = CompletedCycle; + } + + bool completedCycle() const { + return m_runState == CompletedCycle; + } + ITracker& currentTracker() { + return *m_currentTracker; + } + void setCurrentTracker( ITracker* tracker ) { + m_currentTracker = tracker; + } + }; + + class TrackerBase : public ITracker { + protected: + enum CycleState { + NotStarted, + Executing, + ExecutingChildren, + NeedsAnotherRun, + CompletedSuccessfully, + Failed + }; + class TrackerHasName { + std::string m_name; + public: + TrackerHasName( std::string const& name ) : m_name( name ) {} + bool operator ()( Ptr const& tracker ) { + return tracker->name() == m_name; + } + }; + typedef std::vector > Children; + std::string m_name; + TrackerContext& m_ctx; + ITracker* m_parent; + Children m_children; + CycleState m_runState; + public: + TrackerBase( std::string const& name, TrackerContext& ctx, ITracker* parent ) + : m_name( name ), + m_ctx( ctx ), + m_parent( parent ), + m_runState( NotStarted ) + {} + virtual ~TrackerBase(); + + virtual std::string name() const CATCH_OVERRIDE { + return m_name; + } + virtual bool isComplete() const CATCH_OVERRIDE { + return m_runState == CompletedSuccessfully || m_runState == Failed; + } + virtual bool isSuccessfullyCompleted() const CATCH_OVERRIDE { + return m_runState == CompletedSuccessfully; + } + virtual bool isOpen() const CATCH_OVERRIDE { + return m_runState != NotStarted && !isComplete(); + } + virtual bool hasChildren() const CATCH_OVERRIDE { return !m_children.empty(); } - private: - std::string m_name; - RunState m_runState; - TrackedSections m_children; - TrackedSection* m_parent; - }; + virtual void addChild( Ptr const& child ) CATCH_OVERRIDE { + m_children.push_back( child ); + } - inline TrackedSection* TrackedSection::findChild( std::string const& childName ) { - TrackedSections::iterator it = m_children.find( childName ); - return it != m_children.end() - ? &it->second - : NULL; - } - inline TrackedSection* TrackedSection::acquireChild( std::string const& childName ) { - if( TrackedSection* child = findChild( childName ) ) - return child; - m_children.insert( std::make_pair( childName, TrackedSection( childName, this ) ) ); - return findChild( childName ); - } - inline void TrackedSection::leave() { - for( TrackedSections::const_iterator it = m_children.begin(), itEnd = m_children.end(); - it != itEnd; - ++it ) - if( it->second.runState() != Completed ) { + virtual ITracker* findChild( std::string const& name ) CATCH_OVERRIDE { + Children::const_iterator it = std::find_if( m_children.begin(), m_children.end(), TrackerHasName( name ) ); + return( it != m_children.end() ) + ? it->get() + : CATCH_NULL; + } + virtual ITracker& parent() CATCH_OVERRIDE { + assert( m_parent ); // Should always be non-null except for root + return *m_parent; + } + + virtual void openChild() CATCH_OVERRIDE { + if( m_runState != ExecutingChildren ) { m_runState = ExecutingChildren; - return; + if( m_parent ) + m_parent->openChild(); } - m_runState = Completed; - } - - class TestCaseTracker { - public: - TestCaseTracker( std::string const& testCaseName ) - : m_testCase( testCaseName, NULL ), - m_currentSection( &m_testCase ), - m_completedASectionThisRun( false ) - {} - - bool enterSection( std::string const& name ) { - TrackedSection* child = m_currentSection->acquireChild( name ); - if( m_completedASectionThisRun || child->runState() == TrackedSection::Completed ) - return false; - - m_currentSection = child; - m_currentSection->enter(); - return true; } - void leaveSection() { - m_currentSection->leave(); - m_currentSection = m_currentSection->getParent(); - assert( m_currentSection != NULL ); - m_completedASectionThisRun = true; + void open() { + m_runState = Executing; + moveToThis(); + if( m_parent ) + m_parent->openChild(); } - bool currentSectionHasChildren() const { - return m_currentSection->hasChildren(); - } - bool isCompleted() const { - return m_testCase.runState() == TrackedSection::Completed; - } + virtual void close() CATCH_OVERRIDE { - class Guard { - public: - Guard( TestCaseTracker& tracker ) : m_tracker( tracker ) { - m_tracker.enterTestCase(); + // Close any still open children (e.g. generators) + while( &m_ctx.currentTracker() != this ) + m_ctx.currentTracker().close(); + + switch( m_runState ) { + case NotStarted: + case CompletedSuccessfully: + case Failed: + throw std::logic_error( "Illogical state" ); + + case NeedsAnotherRun: + break;; + + case Executing: + m_runState = CompletedSuccessfully; + break; + case ExecutingChildren: + if( m_children.empty() || m_children.back()->isComplete() ) + m_runState = CompletedSuccessfully; + break; + + default: + throw std::logic_error( "Unexpected state" ); } - ~Guard() { - m_tracker.leaveTestCase(); - } - private: - Guard( Guard const& ); - void operator = ( Guard const& ); - TestCaseTracker& m_tracker; - }; - + moveToParent(); + m_ctx.completeCycle(); + } + virtual void fail() CATCH_OVERRIDE { + m_runState = Failed; + if( m_parent ) + m_parent->markAsNeedingAnotherRun(); + moveToParent(); + m_ctx.completeCycle(); + } + virtual void markAsNeedingAnotherRun() CATCH_OVERRIDE { + m_runState = NeedsAnotherRun; + } private: - void enterTestCase() { - m_currentSection = &m_testCase; - m_completedASectionThisRun = false; - m_testCase.enter(); + void moveToParent() { + assert( m_parent ); + m_ctx.setCurrentTracker( m_parent ); } - void leaveTestCase() { - m_testCase.leave(); + void moveToThis() { + m_ctx.setCurrentTracker( this ); } - - TrackedSection m_testCase; - TrackedSection* m_currentSection; - bool m_completedASectionThisRun; }; -} // namespace SectionTracking + class SectionTracker : public TrackerBase { + public: + SectionTracker( std::string const& name, TrackerContext& ctx, ITracker* parent ) + : TrackerBase( name, ctx, parent ) + {} + virtual ~SectionTracker(); -using SectionTracking::TestCaseTracker; + static SectionTracker& acquire( TrackerContext& ctx, std::string const& name ) { + SectionTracker* section = CATCH_NULL; + + ITracker& currentTracker = ctx.currentTracker(); + if( ITracker* childTracker = currentTracker.findChild( name ) ) { + section = dynamic_cast( childTracker ); + assert( section ); + } + else { + section = new SectionTracker( name, ctx, ¤tTracker ); + currentTracker.addChild( section ); + } + if( !ctx.completedCycle() && !section->isComplete() ) { + + section->open(); + } + return *section; + } + }; + + class IndexTracker : public TrackerBase { + int m_size; + int m_index; + public: + IndexTracker( std::string const& name, TrackerContext& ctx, ITracker* parent, int size ) + : TrackerBase( name, ctx, parent ), + m_size( size ), + m_index( -1 ) + {} + virtual ~IndexTracker(); + + static IndexTracker& acquire( TrackerContext& ctx, std::string const& name, int size ) { + IndexTracker* tracker = CATCH_NULL; + + ITracker& currentTracker = ctx.currentTracker(); + if( ITracker* childTracker = currentTracker.findChild( name ) ) { + tracker = dynamic_cast( childTracker ); + assert( tracker ); + } + else { + tracker = new IndexTracker( name, ctx, ¤tTracker, size ); + currentTracker.addChild( tracker ); + } + + if( !ctx.completedCycle() && !tracker->isComplete() ) { + if( tracker->m_runState != ExecutingChildren && tracker->m_runState != NeedsAnotherRun ) + tracker->moveNext(); + tracker->open(); + } + + return *tracker; + } + + int index() const { return m_index; } + + void moveNext() { + m_index++; + m_children.clear(); + } + + virtual void close() CATCH_OVERRIDE { + TrackerBase::close(); + if( m_runState == CompletedSuccessfully && m_index < m_size-1 ) + m_runState = Executing; + } + }; + + inline ITracker& TrackerContext::startRun() { + m_rootTracker = new SectionTracker( "{root}", *this, CATCH_NULL ); + m_currentTracker = CATCH_NULL; + m_runState = Executing; + return *m_rootTracker; + } + +} // namespace TestCaseTracking + +using TestCaseTracking::ITracker; +using TestCaseTracking::TrackerContext; +using TestCaseTracking::SectionTracker; +using TestCaseTracking::IndexTracker; } // namespace Catch @@ -5174,15 +5643,12 @@ namespace Catch { public: - explicit RunContext( Ptr const& config, Ptr const& reporter ) - : m_runInfo( config->name() ), + explicit RunContext( Ptr const& _config, Ptr const& reporter ) + : m_runInfo( _config->name() ), m_context( getCurrentMutableContext() ), - m_activeTestCase( NULL ), - m_config( config ), - m_reporter( reporter ), - m_prevRunner( m_context.getRunner() ), - m_prevResultCapture( m_context.getResultCapture() ), - m_prevConfig( m_context.getConfig() ) + m_activeTestCase( CATCH_NULL ), + m_config( _config ), + m_reporter( reporter ) { m_context.setRunner( this ); m_context.setConfig( m_config ); @@ -5192,10 +5658,6 @@ namespace Catch { virtual ~RunContext() { m_reporter->testRunEnded( TestRunStats( m_runInfo, m_totals, aborting() ) ); - m_context.setRunner( m_prevRunner ); - m_context.setConfig( NULL ); - m_context.setResultCapture( m_prevResultCapture ); - m_context.setConfig( m_prevConfig ); } void testGroupStarting( std::string const& testSpec, std::size_t groupIndex, std::size_t groupsCount ) { @@ -5216,14 +5678,17 @@ namespace Catch { m_reporter->testCaseStarting( testInfo ); m_activeTestCase = &testCase; - m_testCaseTracker = TestCaseTracker( testInfo.name ); do { + m_trackerContext.startRun(); do { + m_trackerContext.startCycle(); + m_testCaseTracker = &SectionTracker::acquire( m_trackerContext, testInfo.name ); runCurrentTest( redirectedCout, redirectedCerr ); } - while( !m_testCaseTracker->isCompleted() && !aborting() ); + while( !m_testCaseTracker->isSuccessfullyCompleted() && !aborting() ); } + // !TBD: deprecated - this will be replaced by indexed trackers while( getCurrentContext().advanceGeneratorsForCurrentTest() && !aborting() ); Totals deltaTotals = m_totals.delta( prevTotals ); @@ -5234,8 +5699,8 @@ namespace Catch { redirectedCerr, aborting() ) ); - m_activeTestCase = NULL; - m_testCaseTracker.reset(); + m_activeTestCase = CATCH_NULL; + m_testCaseTracker = CATCH_NULL; return deltaTotals; } @@ -5270,8 +5735,10 @@ namespace Catch { std::ostringstream oss; oss << sectionInfo.name << "@" << sectionInfo.lineInfo; - if( !m_testCaseTracker->enterSection( oss.str() ) ) + ITracker& sectionTracker = SectionTracker::acquire( m_trackerContext, oss.str() ); + if( !sectionTracker.isOpen() ) return false; + m_activeSections.push_back( §ionTracker ); m_lastAssertionInfo.lineInfo = sectionInfo.lineInfo; @@ -5282,30 +5749,40 @@ namespace Catch { return true; } bool testForMissingAssertions( Counts& assertions ) { - if( assertions.total() != 0 || - !m_config->warnAboutMissingAssertions() || - m_testCaseTracker->currentSectionHasChildren() ) + if( assertions.total() != 0 ) + return false; + if( !m_config->warnAboutMissingAssertions() ) + return false; + if( m_trackerContext.currentTracker().hasChildren() ) return false; m_totals.assertions.failed++; assertions.failed++; return true; } - virtual void sectionEnded( SectionInfo const& info, Counts const& prevAssertions, double _durationInSeconds ) { - if( std::uncaught_exception() ) { - m_unfinishedSections.push_back( UnfinishedSections( info, prevAssertions, _durationInSeconds ) ); - return; - } - - Counts assertions = m_totals.assertions - prevAssertions; + virtual void sectionEnded( SectionEndInfo const& endInfo ) { + Counts assertions = m_totals.assertions - endInfo.prevAssertions; bool missingAssertions = testForMissingAssertions( assertions ); - m_testCaseTracker->leaveSection(); + if( !m_activeSections.empty() ) { + m_activeSections.back()->close(); + m_activeSections.pop_back(); + } - m_reporter->sectionEnded( SectionStats( info, assertions, _durationInSeconds, missingAssertions ) ); + m_reporter->sectionEnded( SectionStats( endInfo.sectionInfo, assertions, endInfo.durationInSeconds, missingAssertions ) ); m_messages.clear(); } + virtual void sectionEndedEarly( SectionEndInfo const& endInfo ) { + if( m_unfinishedSections.empty() ) + m_activeSections.back()->fail(); + else + m_activeSections.back()->close(); + m_activeSections.pop_back(); + + m_unfinishedSections.push_back( endInfo ); + } + virtual void pushScopedMessage( MessageInfo const& message ) { m_messages.push_back( message ); } @@ -5371,7 +5848,8 @@ namespace Catch { double duration = 0; try { m_lastAssertionInfo = AssertionInfo( "TEST_CASE", testCaseInfo.lineInfo, "", ResultDisposition::Normal ); - TestCaseTracker::Guard guard( *m_testCaseTracker ); + + seedRng( *m_config ); Timer timer; timer.start(); @@ -5391,6 +5869,7 @@ namespace Catch { catch(...) { makeUnexpectedResultBuilder().useActiveException(); } + m_testCaseTracker->close(); handleUnfinishedSections(); m_messages.clear(); @@ -5425,39 +5904,29 @@ namespace Catch { void handleUnfinishedSections() { // If sections ended prematurely due to an exception we stored their // infos here so we can tear them down outside the unwind process. - for( std::vector::const_reverse_iterator it = m_unfinishedSections.rbegin(), + for( std::vector::const_reverse_iterator it = m_unfinishedSections.rbegin(), itEnd = m_unfinishedSections.rend(); it != itEnd; ++it ) - sectionEnded( it->info, it->prevAssertions, it->durationInSeconds ); + sectionEnded( *it ); m_unfinishedSections.clear(); } - struct UnfinishedSections { - UnfinishedSections( SectionInfo const& _info, Counts const& _prevAssertions, double _durationInSeconds ) - : info( _info ), prevAssertions( _prevAssertions ), durationInSeconds( _durationInSeconds ) - {} - - SectionInfo info; - Counts prevAssertions; - double durationInSeconds; - }; - TestRunInfo m_runInfo; IMutableContext& m_context; TestCase const* m_activeTestCase; - Option m_testCaseTracker; + ITracker* m_testCaseTracker; + ITracker* m_currentSectionTracker; AssertionResult m_lastResult; Ptr m_config; Totals m_totals; Ptr m_reporter; std::vector m_messages; - IRunner* m_prevRunner; - IResultCapture* m_prevResultCapture; - Ptr m_prevConfig; AssertionInfo m_lastAssertionInfo; - std::vector m_unfinishedSections; + std::vector m_unfinishedSections; + std::vector m_activeSections; + TrackerContext m_trackerContext; }; IResultCapture& getResultCapture() { @@ -5505,89 +5974,87 @@ namespace Catch { namespace Catch { - class Runner { + Ptr createReporter( std::string const& reporterName, Ptr const& config ) { + Ptr reporter = getRegistryHub().getReporterRegistry().create( reporterName, config.get() ); + if( !reporter ) { + std::ostringstream oss; + oss << "No reporter registered with name: '" << reporterName << "'"; + throw std::domain_error( oss.str() ); + } + return reporter; + } - public: - Runner( Ptr const& config ) - : m_config( config ) - { - openStream(); - makeReporter(); + Ptr makeReporter( Ptr const& config ) { + std::vector reporters = config->getReporterNames(); + if( reporters.empty() ) + reporters.push_back( "console" ); + + Ptr reporter; + for( std::vector::const_iterator it = reporters.begin(), itEnd = reporters.end(); + it != itEnd; + ++it ) + reporter = addReporter( reporter, createReporter( *it, config ) ); + return reporter; + } + Ptr addListeners( Ptr const& config, Ptr reporters ) { + IReporterRegistry::Listeners listeners = getRegistryHub().getReporterRegistry().getListeners(); + for( IReporterRegistry::Listeners::const_iterator it = listeners.begin(), itEnd = listeners.end(); + it != itEnd; + ++it ) + reporters = addReporter(reporters, (*it)->create( ReporterConfig( config ) ) ); + return reporters; + } + + Totals runTests( Ptr const& config ) { + + Ptr iconfig = config.get(); + + Ptr reporter = makeReporter( config ); + reporter = addListeners( iconfig, reporter ); + + RunContext context( iconfig, reporter ); + + Totals totals; + + context.testGroupStarting( config->name(), 1, 1 ); + + TestSpec testSpec = config->testSpec(); + if( !testSpec.hasFilters() ) + testSpec = TestSpecParser( ITagAliasRegistry::get() ).parse( "~[.]" ).testSpec(); // All not hidden tests + + std::vector const& allTestCases = getAllTestCasesSorted( *iconfig ); + for( std::vector::const_iterator it = allTestCases.begin(), itEnd = allTestCases.end(); + it != itEnd; + ++it ) { + if( !context.aborting() && matchTest( *it, testSpec, *iconfig ) ) + totals += context.runTest( *it ); + else + reporter->skipTest( *it ); } - Totals runTests() { + context.testGroupEnded( iconfig->name(), totals, 1, 1 ); + return totals; + } - RunContext context( m_config.get(), m_reporter ); + void applyFilenamesAsTags( IConfig const& config ) { + std::vector const& tests = getAllTestCasesSorted( config ); + for(std::size_t i = 0; i < tests.size(); ++i ) { + TestCase& test = const_cast( tests[i] ); + std::set tags = test.tags; - Totals totals; + std::string filename = test.lineInfo.file; + std::string::size_type lastSlash = filename.find_last_of( "\\/" ); + if( lastSlash != std::string::npos ) + filename = filename.substr( lastSlash+1 ); - context.testGroupStarting( "all tests", 1, 1 ); // deprecated? + std::string::size_type lastDot = filename.find_last_of( "." ); + if( lastDot != std::string::npos ) + filename = filename.substr( 0, lastDot ); - TestSpec testSpec = m_config->testSpec(); - if( !testSpec.hasFilters() ) - testSpec = TestSpecParser( ITagAliasRegistry::get() ).parse( "~[.]" ).testSpec(); // All not hidden tests - - std::vector testCases; - getRegistryHub().getTestCaseRegistry().getFilteredTests( testSpec, *m_config, testCases ); - - int testsRunForGroup = 0; - for( std::vector::const_iterator it = testCases.begin(), itEnd = testCases.end(); - it != itEnd; - ++it ) { - testsRunForGroup++; - if( m_testsAlreadyRun.find( *it ) == m_testsAlreadyRun.end() ) { - - if( context.aborting() ) - break; - - totals += context.runTest( *it ); - m_testsAlreadyRun.insert( *it ); - } - } - std::vector skippedTestCases; - getRegistryHub().getTestCaseRegistry().getFilteredTests( testSpec, *m_config, skippedTestCases, true ); - - for( std::vector::const_iterator it = skippedTestCases.begin(), itEnd = skippedTestCases.end(); - it != itEnd; - ++it ) - m_reporter->skipTest( *it ); - - context.testGroupEnded( "all tests", totals, 1, 1 ); - return totals; + tags.insert( "#" + filename ); + setTags( test, tags ); } - - private: - void openStream() { - // Open output file, if specified - if( !m_config->getFilename().empty() ) { - m_ofs.open( m_config->getFilename().c_str() ); - if( m_ofs.fail() ) { - std::ostringstream oss; - oss << "Unable to open file: '" << m_config->getFilename() << "'"; - throw std::domain_error( oss.str() ); - } - m_config->setStreamBuf( m_ofs.rdbuf() ); - } - } - void makeReporter() { - std::string reporterName = m_config->getReporterName().empty() - ? "console" - : m_config->getReporterName(); - - m_reporter = getRegistryHub().getReporterRegistry().create( reporterName, m_config.get() ); - if( !m_reporter ) { - std::ostringstream oss; - oss << "No reporter registered with name: '" << reporterName << "'"; - throw std::domain_error( oss.str() ); - } - } - - private: - Ptr m_config; - std::ofstream m_ofs; - Ptr m_reporter; - std::set m_testsAlreadyRun; - }; + } class Session : NonCopyable { static bool alreadyInstantiated; @@ -5616,7 +6083,7 @@ namespace Catch { Catch::cout() << "For more detail usage please see the project docs\n" << std::endl; } - int applyCommandLine( int argc, char* const argv[], OnUnusedOptions::DoWhat unusedOptionBehaviour = OnUnusedOptions::Fail ) { + int applyCommandLine( int argc, char const* const argv[], OnUnusedOptions::DoWhat unusedOptionBehaviour = OnUnusedOptions::Fail ) { try { m_cli.setThrowOnUnrecognisedTokens( unusedOptionBehaviour == OnUnusedOptions::Fail ); m_unusedTokens = m_cli.parseInto( argc, argv, m_configData ); @@ -5643,7 +6110,7 @@ namespace Catch { m_config.reset(); } - int run( int argc, char* const argv[] ) { + int run( int argc, char const* const argv[] ) { int returnCode = applyCommandLine( argc, argv ); if( returnCode == 0 ) @@ -5659,15 +6126,16 @@ namespace Catch { { config(); // Force config to be constructed - std::srand( m_configData.rngSeed ); + seedRng( *m_config ); - Runner runner( m_config ); + if( m_configData.filenamesAsTags ) + applyFilenamesAsTags( *m_config ); // Handle list request if( Option listed = list( config() ) ) return static_cast( *listed ); - return static_cast( runner.runTests().assertions.failed ); + return static_cast( runTests( m_config ).assertions.failed ); } catch( std::exception& ex ) { Catch::cerr() << ex.what() << std::endl; @@ -5689,7 +6157,6 @@ namespace Catch { m_config = new Config( m_configData ); return *m_config; } - private: Clara::CommandLine m_cli; std::vector m_unusedTokens; @@ -5715,16 +6182,76 @@ namespace Catch { namespace Catch { - class TestRegistry : public ITestCaseRegistry { - struct LexSort { - bool operator() (TestCase i,TestCase j) const { return (i sortTests( IConfig const& config, std::vector const& unsortedTestCases ) { + + std::vector sorted = unsortedTestCases; + + switch( config.runOrder() ) { + case RunTests::InLexicographicalOrder: + std::sort( sorted.begin(), sorted.end(), LexSort() ); + break; + case RunTests::InRandomOrder: + { + seedRng( config ); + + RandomNumberGenerator rng; + std::random_shuffle( sorted.begin(), sorted.end(), rng ); + } + break; + case RunTests::InDeclarationOrder: + // already in declaration order + break; + } + return sorted; + } + bool matchTest( TestCase const& testCase, TestSpec const& testSpec, IConfig const& config ) { + return testSpec.matches( testCase ) && ( config.allowThrows() || !testCase.throws() ); + } + + void enforceNoDuplicateTestCases( std::vector const& functions ) { + std::set seenFunctions; + for( std::vector::const_iterator it = functions.begin(), itEnd = functions.end(); + it != itEnd; + ++it ) { + std::pair::const_iterator, bool> prev = seenFunctions.insert( *it ); + if( !prev.second ){ + Catch::cerr() + << Colour( Colour::Red ) + << "error: TEST_CASE( \"" << it->name << "\" ) already defined.\n" + << "\tFirst seen at " << prev.first->getTestCaseInfo().lineInfo << "\n" + << "\tRedefined at " << it->getTestCaseInfo().lineInfo << std::endl; + exit(1); + } + } + } + + std::vector filterTests( std::vector const& testCases, TestSpec const& testSpec, IConfig const& config ) { + std::vector filtered; + filtered.reserve( testCases.size() ); + for( std::vector::const_iterator it = testCases.begin(), itEnd = testCases.end(); + it != itEnd; + ++it ) + if( matchTest( *it, testSpec, config ) ) + filtered.push_back( *it ); + return filtered; + } + std::vector const& getAllTestCasesSorted( IConfig const& config ) { + return getRegistryHub().getTestCaseRegistry().getAllTestsSorted( config ); + } + + class TestRegistry : public ITestCaseRegistry { public: - TestRegistry() : m_unnamedCount( 0 ) {} + TestRegistry() + : m_currentSortOrder( RunTests::InDeclarationOrder ), + m_unnamedCount( 0 ) + {} virtual ~TestRegistry(); virtual void registerTest( TestCase const& testCase ) { @@ -5734,69 +6261,29 @@ namespace Catch { oss << "Anonymous test case " << ++m_unnamedCount; return registerTest( testCase.withName( oss.str() ) ); } - - if( m_functions.find( testCase ) == m_functions.end() ) { - m_functions.insert( testCase ); - m_functionsInOrder.push_back( testCase ); - if( !testCase.isHidden() ) - m_nonHiddenFunctions.push_back( testCase ); - } - else { - TestCase const& prev = *m_functions.find( testCase ); - { - Colour colourGuard( Colour::Red ); - Catch::cerr() << "error: TEST_CASE( \"" << name << "\" ) already defined.\n" - << "\tFirst seen at " << prev.getTestCaseInfo().lineInfo << "\n" - << "\tRedefined at " << testCase.getTestCaseInfo().lineInfo << std::endl; - } - exit(1); - } + m_functions.push_back( testCase ); } virtual std::vector const& getAllTests() const { - return m_functionsInOrder; + return m_functions; } + virtual std::vector const& getAllTestsSorted( IConfig const& config ) const { + if( m_sortedFunctions.empty() ) + enforceNoDuplicateTestCases( m_functions ); - virtual std::vector const& getAllNonHiddenTests() const { - return m_nonHiddenFunctions; - } - - virtual void getFilteredTests( TestSpec const& testSpec, IConfig const& config, std::vector& matchingTestCases, bool negated = false ) const { - - for( std::vector::const_iterator it = m_functionsInOrder.begin(), - itEnd = m_functionsInOrder.end(); - it != itEnd; - ++it ) { - bool includeTest = testSpec.matches( *it ) && ( config.allowThrows() || !it->throws() ); - if( includeTest != negated ) - matchingTestCases.push_back( *it ); + if( m_currentSortOrder != config.runOrder() || m_sortedFunctions.empty() ) { + m_sortedFunctions = sortTests( config, m_functions ); + m_currentSortOrder = config.runOrder(); } - sortTests( config, matchingTestCases ); + return m_sortedFunctions; } private: - - static void sortTests( IConfig const& config, std::vector& matchingTestCases ) { - - switch( config.runOrder() ) { - case RunTests::InLexicographicalOrder: - std::sort( matchingTestCases.begin(), matchingTestCases.end(), LexSort() ); - break; - case RunTests::InRandomOrder: - { - RandomNumberGenerator rng; - std::random_shuffle( matchingTestCases.begin(), matchingTestCases.end(), rng ); - } - break; - case RunTests::InDeclarationOrder: - // already in declaration order - break; - } - } - std::set m_functions; - std::vector m_functionsInOrder; - std::vector m_nonHiddenFunctions; + std::vector m_functions; + mutable RunTests::InWhatOrder m_currentSortOrder; + mutable std::vector m_sortedFunctions; size_t m_unnamedCount; + std::ios_base::Init m_ostreamInit; // Forces cout/ cerr to be initialised }; /////////////////////////////////////////////////////////////////////////// @@ -5829,29 +6316,38 @@ namespace Catch { return className; } - /////////////////////////////////////////////////////////////////////////// + void registerTestCase + ( ITestCase* testCase, + char const* classOrQualifiedMethodName, + NameAndDesc const& nameAndDesc, + SourceLineInfo const& lineInfo ) { - AutoReg::AutoReg( TestFunction function, - SourceLineInfo const& lineInfo, - NameAndDesc const& nameAndDesc ) { + getMutableRegistryHub().registerTest + ( makeTestCase + ( testCase, + extractClassName( classOrQualifiedMethodName ), + nameAndDesc.name, + nameAndDesc.description, + lineInfo ) ); + } + void registerTestCaseFunction + ( TestFunction function, + SourceLineInfo const& lineInfo, + NameAndDesc const& nameAndDesc ) { registerTestCase( new FreeFunctionTestCase( function ), "", nameAndDesc, lineInfo ); } - AutoReg::~AutoReg() {} + /////////////////////////////////////////////////////////////////////////// - void AutoReg::registerTestCase( ITestCase* testCase, - char const* classOrQualifiedMethodName, - NameAndDesc const& nameAndDesc, - SourceLineInfo const& lineInfo ) { - - getMutableRegistryHub().registerTest - ( makeTestCase( testCase, - extractClassName( classOrQualifiedMethodName ), - nameAndDesc.name, - nameAndDesc.description, - lineInfo ) ); + AutoReg::AutoReg + ( TestFunction function, + SourceLineInfo const& lineInfo, + NameAndDesc const& nameAndDesc ) { + registerTestCaseFunction( function, lineInfo, nameAndDesc ); } + AutoReg::~AutoReg() {} + } // end namespace Catch // #included from: catch_reporter_registry.hpp @@ -5865,27 +6361,32 @@ namespace Catch { public: - virtual ~ReporterRegistry() { - deleteAllValues( m_factories ); - } + virtual ~ReporterRegistry() CATCH_OVERRIDE {} - virtual IStreamingReporter* create( std::string const& name, Ptr const& config ) const { + virtual IStreamingReporter* create( std::string const& name, Ptr const& config ) const CATCH_OVERRIDE { FactoryMap::const_iterator it = m_factories.find( name ); if( it == m_factories.end() ) - return NULL; + return CATCH_NULL; return it->second->create( ReporterConfig( config ) ); } - void registerReporter( std::string const& name, IReporterFactory* factory ) { + void registerReporter( std::string const& name, Ptr const& factory ) { m_factories.insert( std::make_pair( name, factory ) ); } + void registerListener( Ptr const& factory ) { + m_listeners.push_back( factory ); + } - FactoryMap const& getFactories() const { + virtual FactoryMap const& getFactories() const CATCH_OVERRIDE { return m_factories; } + virtual Listeners const& getListeners() const CATCH_OVERRIDE { + return m_listeners; + } private: FactoryMap m_factories; + Listeners m_listeners; }; } @@ -5913,13 +6414,13 @@ namespace Catch { #ifdef __OBJC__ // In Objective-C try objective-c exceptions first @try { - throw; + return tryTranslators(); } @catch (NSException *exception) { return Catch::toString( [exception description] ); } #else - throw; + return tryTranslators(); #endif } catch( TestFailureException& ) { @@ -5935,20 +6436,15 @@ namespace Catch { return msg; } catch(...) { - return tryTranslators( m_translators.begin() ); + return "Unknown exception"; } } - std::string tryTranslators( std::vector::const_iterator it ) const { - if( it == m_translators.end() ) - return "Unknown exception"; - - try { - return (*it)->translate(); - } - catch(...) { - return tryTranslators( it+1 ); - } + std::string tryTranslators() const { + if( m_translators.empty() ) + throw; + else + return m_translators[0]->translate( m_translators.begin()+1, m_translators.end() ); } private: @@ -5968,24 +6464,27 @@ namespace Catch { public: // IRegistryHub RegistryHub() { } - virtual IReporterRegistry const& getReporterRegistry() const { + virtual IReporterRegistry const& getReporterRegistry() const CATCH_OVERRIDE { return m_reporterRegistry; } - virtual ITestCaseRegistry const& getTestCaseRegistry() const { + virtual ITestCaseRegistry const& getTestCaseRegistry() const CATCH_OVERRIDE { return m_testCaseRegistry; } - virtual IExceptionTranslatorRegistry& getExceptionTranslatorRegistry() { + virtual IExceptionTranslatorRegistry& getExceptionTranslatorRegistry() CATCH_OVERRIDE { return m_exceptionTranslatorRegistry; } public: // IMutableRegistryHub - virtual void registerReporter( std::string const& name, IReporterFactory* factory ) { + virtual void registerReporter( std::string const& name, Ptr const& factory ) CATCH_OVERRIDE { m_reporterRegistry.registerReporter( name, factory ); } - virtual void registerTest( TestCase const& testInfo ) { + virtual void registerListener( Ptr const& factory ) CATCH_OVERRIDE { + m_reporterRegistry.registerListener( factory ); + } + virtual void registerTest( TestCase const& testInfo ) CATCH_OVERRIDE { m_testCaseRegistry.registerTest( testInfo ); } - virtual void registerTranslator( const IExceptionTranslator* translator ) { + virtual void registerTranslator( const IExceptionTranslator* translator ) CATCH_OVERRIDE { m_exceptionTranslatorRegistry.registerTranslator( translator ); } @@ -5997,7 +6496,7 @@ namespace Catch { // Single, global, instance inline RegistryHub*& getTheRegistryHub() { - static RegistryHub* theRegistryHub = NULL; + static RegistryHub* theRegistryHub = CATCH_NULL; if( !theRegistryHub ) theRegistryHub = new RegistryHub(); return theRegistryHub; @@ -6012,7 +6511,7 @@ namespace Catch { } void cleanUp() { delete getTheRegistryHub(); - getTheRegistryHub() = NULL; + getTheRegistryHub() = CATCH_NULL; cleanUpContext(); } std::string translateActiveException() { @@ -6048,19 +6547,6 @@ namespace Catch { // #included from: catch_stream.hpp #define TWOBLUECUBES_CATCH_STREAM_HPP_INCLUDED -// #included from: catch_streambuf.h -#define TWOBLUECUBES_CATCH_STREAMBUF_H_INCLUDED - -#include - -namespace Catch { - - class StreamBufBase : public std::streambuf { - public: - virtual ~StreamBufBase() CATCH_NOEXCEPT; - }; -} - #include #include #include @@ -6105,6 +6591,19 @@ namespace Catch { /////////////////////////////////////////////////////////////////////////// + FileStream::FileStream( std::string const& filename ) { + m_ofs.open( filename.c_str() ); + if( m_ofs.fail() ) { + std::ostringstream oss; + oss << "Unable to open file: '" << filename << "'"; + throw std::domain_error( oss.str() ); + } + } + + std::ostream& FileStream::stream() const { + return m_ofs; + } + struct OutputDebugWriter { void operator()( std::string const&str ) { @@ -6112,23 +6611,26 @@ namespace Catch { } }; - Stream::Stream() - : streamBuf( NULL ), isOwned( false ) + DebugOutStream::DebugOutStream() + : m_streamBuf( new StreamBufImpl() ), + m_os( m_streamBuf.get() ) {} - Stream::Stream( std::streambuf* _streamBuf, bool _isOwned ) - : streamBuf( _streamBuf ), isOwned( _isOwned ) - {} - - void Stream::release() { - if( isOwned ) { - delete streamBuf; - streamBuf = NULL; - isOwned = false; - } + std::ostream& DebugOutStream::stream() const { + return m_os; } -#ifndef CATCH_CONFIG_NOSTDOUT // If you #define this you must implement this functions + // Store the streambuf from cout up-front because + // cout may get redirected when running tests + CoutStream::CoutStream() + : m_os( Catch::cout().rdbuf() ) + {} + + std::ostream& CoutStream::stream() const { + return m_os; + } + +#ifndef CATCH_CONFIG_NOSTDOUT // If you #define this you must implement these functions std::ostream& cout() { return std::cout; } @@ -6142,7 +6644,7 @@ namespace Catch { class Context : public IMutableContext { - Context() : m_config( NULL ), m_runner( NULL ), m_resultCapture( NULL ) {} + Context() : m_config( CATCH_NULL ), m_runner( CATCH_NULL ), m_resultCapture( CATCH_NULL ) {} Context( Context const& ); void operator=( Context const& ); @@ -6188,7 +6690,7 @@ namespace Catch { m_generatorsByTestName.find( testName ); return it != m_generatorsByTestName.end() ? it->second - : NULL; + : CATCH_NULL; } IGeneratorsForTest& getGeneratorsForCurrentTest() { @@ -6209,7 +6711,7 @@ namespace Catch { }; namespace { - Context* currentContext = NULL; + Context* currentContext = CATCH_NULL; } IMutableContext& getCurrentMutableContext() { if( !currentContext ) @@ -6220,17 +6722,9 @@ namespace Catch { return getCurrentMutableContext(); } - Stream createStream( std::string const& streamName ) { - if( streamName == "stdout" ) return Stream( Catch::cout().rdbuf(), false ); - if( streamName == "stderr" ) return Stream( Catch::cerr().rdbuf(), false ); - if( streamName == "debug" ) return Stream( new StreamBufImpl, true ); - - throw std::domain_error( "Unknown stream: " + streamName ); - } - void cleanUpContext() { delete currentContext; - currentContext = NULL; + currentContext = CATCH_NULL; } } @@ -6286,12 +6780,13 @@ namespace { { CONSOLE_SCREEN_BUFFER_INFO csbiInfo; GetConsoleScreenBufferInfo( stdoutHandle, &csbiInfo ); - originalAttributes = csbiInfo.wAttributes; + originalForegroundAttributes = csbiInfo.wAttributes & ~( BACKGROUND_GREEN | BACKGROUND_RED | BACKGROUND_BLUE | BACKGROUND_INTENSITY ); + originalBackgroundAttributes = csbiInfo.wAttributes & ~( FOREGROUND_GREEN | FOREGROUND_RED | FOREGROUND_BLUE | FOREGROUND_INTENSITY ); } virtual void use( Colour::Code _colourCode ) { switch( _colourCode ) { - case Colour::None: return setTextAttribute( originalAttributes ); + case Colour::None: return setTextAttribute( originalForegroundAttributes ); case Colour::White: return setTextAttribute( FOREGROUND_GREEN | FOREGROUND_RED | FOREGROUND_BLUE ); case Colour::Red: return setTextAttribute( FOREGROUND_RED ); case Colour::Green: return setTextAttribute( FOREGROUND_GREEN ); @@ -6311,10 +6806,11 @@ namespace { private: void setTextAttribute( WORD _textAttribute ) { - SetConsoleTextAttribute( stdoutHandle, _textAttribute ); + SetConsoleTextAttribute( stdoutHandle, _textAttribute | originalBackgroundAttributes ); } HANDLE stdoutHandle; - WORD originalAttributes; + WORD originalForegroundAttributes; + WORD originalBackgroundAttributes; }; IColourImpl* platformColourInstance() { @@ -6640,6 +7136,21 @@ namespace Catch { return TestCase( _testCase, info ); } + void setTags( TestCaseInfo& testCaseInfo, std::set const& tags ) + { + testCaseInfo.tags = tags; + testCaseInfo.lcaseTags.clear(); + + std::ostringstream oss; + for( std::set::const_iterator it = tags.begin(), itEnd = tags.end(); it != itEnd; ++it ) { + oss << "[" << *it << "]"; + std::string lcaseTag = toLower( *it ); + testCaseInfo.properties = static_cast( testCaseInfo.properties | parseSpecialTag( lcaseTag ) ); + testCaseInfo.lcaseTags.insert( lcaseTag ); + } + testCaseInfo.tagsAsString = oss.str(); + } + TestCaseInfo::TestCaseInfo( std::string const& _name, std::string const& _className, std::string const& _description, @@ -6648,18 +7159,10 @@ namespace Catch { : name( _name ), className( _className ), description( _description ), - tags( _tags ), lineInfo( _lineInfo ), properties( None ) { - std::ostringstream oss; - for( std::set::const_iterator it = _tags.begin(), itEnd = _tags.end(); it != itEnd; ++it ) { - oss << "[" << *it << "]"; - std::string lcaseTag = toLower( *it ); - properties = static_cast( properties | parseSpecialTag( lcaseTag ) ); - lcaseTags.insert( lcaseTag ); - } - tagsAsString = oss.str(); + setTags( *this, _tags ); } TestCaseInfo::TestCaseInfo( TestCaseInfo const& other ) @@ -6767,7 +7270,7 @@ namespace Catch { return os; } - Version libraryVersion( 1, 2, 1, "", 0 ); + Version libraryVersion( 1, 3, 1, "", 0 ); } @@ -6960,7 +7463,7 @@ namespace Catch { #else uint64_t getCurrentTicks() { timeval t; - gettimeofday(&t,NULL); + gettimeofday(&t,CATCH_NULL); return static_cast( t.tv_sec ) * 1000000ull + static_cast( t.tv_usec ); } #endif @@ -7059,6 +7562,14 @@ namespace Catch { return line < other.line || ( line == other.line && file < other.file ); } + void seedRng( IConfig const& config ) { + if( config.rngSeed() != 0 ) + std::srand( config.rngSeed() ); + } + unsigned int rngSeed() { + return getCurrentContext().getConfig()->rngSeed(); + } + std::ostream& operator << ( std::ostream& os, SourceLineInfo const& info ) { #ifndef __GNUG__ os << info.file << "(" << info.line << ")"; @@ -7098,8 +7609,13 @@ namespace Catch { } Section::~Section() { - if( m_sectionIncluded ) - getResultCapture().sectionEnded( m_info, m_assertions, m_timer.getElapsedSeconds() ); + if( m_sectionIncluded ) { + SectionEndInfo endInfo( m_info, m_assertions, m_timer.getElapsedSeconds() ); + if( std::uncaught_exception() ) + getResultCapture().sectionEndedEarly( endInfo ); + else + getResultCapture().sectionEnded( endInfo ); + } } // This indicates whether the section should be executed or not @@ -7151,7 +7667,7 @@ namespace Catch { // Call sysctl. size = sizeof(info); - if( sysctl(mib, sizeof(mib) / sizeof(*mib), &info, &size, NULL, 0) != 0 ) { + if( sysctl(mib, sizeof(mib) / sizeof(*mib), &info, &size, CATCH_NULL, 0) != 0 ) { Catch::cerr() << "\n** Call to sysctl failed - unable to determine if debugger is active **\n" << std::endl; return false; } @@ -7205,9 +7721,11 @@ namespace Catch { namespace Detail { - std::string unprintableString = "{?}"; + const std::string unprintableString = "{?}"; namespace { + const int hexThreshold = 255; + struct Endianness { enum Arch { Big, Little }; @@ -7289,7 +7807,7 @@ std::string toString( wchar_t* const value ) std::string toString( int value ) { std::ostringstream oss; oss << value; - if( value >= 255 ) + if( value > Detail::hexThreshold ) oss << " (0x" << std::hex << value << ")"; return oss.str(); } @@ -7297,7 +7815,7 @@ std::string toString( int value ) { std::string toString( unsigned long value ) { std::ostringstream oss; oss << value; - if( value >= 255 ) + if( value > Detail::hexThreshold ) oss << " (0x" << std::hex << value << ")"; return oss.str(); } @@ -7347,6 +7865,23 @@ std::string toString( unsigned char value ) { return toString( static_cast( value ) ); } +#ifdef CATCH_CONFIG_CPP11_LONG_LONG +std::string toString( long long value ) { + std::ostringstream oss; + oss << value; + if( value > Detail::hexThreshold ) + oss << " (0x" << std::hex << value << ")"; + return oss.str(); +} +std::string toString( unsigned long long value ) { + std::ostringstream oss; + oss << value; + if( value > Detail::hexThreshold ) + oss << " (0x" << std::hex << value << ")"; + return oss.str(); +} +#endif + #ifdef CATCH_CONFIG_CPP11_NULLPTR std::string toString( std::nullptr_t ) { return "nullptr"; @@ -7376,11 +7911,17 @@ std::string toString( std::nullptr_t ) { namespace Catch { + std::string capturedExpressionWithSecondArgument( std::string const& capturedExpression, std::string const& secondArg ) { + return secondArg.empty() || secondArg == "\"\"" + ? capturedExpression + : capturedExpression + ", " + secondArg; + } ResultBuilder::ResultBuilder( char const* macroName, SourceLineInfo const& lineInfo, char const* capturedExpression, - ResultDisposition::Flags resultDisposition ) - : m_assertionInfo( macroName, lineInfo, capturedExpression, resultDisposition ), + ResultDisposition::Flags resultDisposition, + char const* secondArg ) + : m_assertionInfo( macroName, lineInfo, capturedExpressionWithSecondArgument( capturedExpression, secondArg ), resultDisposition ), m_shouldDebugBreak( false ), m_shouldThrow( false ) {} @@ -7421,9 +7962,35 @@ namespace Catch { setResultType( resultType ); captureExpression(); } + void ResultBuilder::captureExpectedException( std::string const& expectedMessage ) { + if( expectedMessage.empty() ) + captureExpectedException( Matchers::Impl::Generic::AllOf() ); + else + captureExpectedException( Matchers::Equals( expectedMessage ) ); + } + + void ResultBuilder::captureExpectedException( Matchers::Impl::Matcher const& matcher ) { + + assert( m_exprComponents.testFalse == false ); + AssertionResultData data = m_data; + data.resultType = ResultWas::Ok; + data.reconstructedExpression = m_assertionInfo.capturedExpression; + + std::string actualMessage = Catch::translateActiveException(); + if( !matcher.match( actualMessage ) ) { + data.resultType = ResultWas::ExpressionFailed; + data.reconstructedExpression = actualMessage; + } + AssertionResult result( m_assertionInfo, data ); + handleResult( result ); + } void ResultBuilder::captureExpression() { AssertionResult result = build(); + handleResult( result ); + } + void ResultBuilder::handleResult( AssertionResult const& result ) + { getResultCapture().assertionEnded( result ); if( !result.isOk() ) { @@ -7576,6 +8143,137 @@ namespace Catch { } // end namespace Catch +// #included from: ../reporters/catch_reporter_multi.hpp +#define TWOBLUECUBES_CATCH_REPORTER_MULTI_HPP_INCLUDED + +namespace Catch { + +class MultipleReporters : public SharedImpl { + typedef std::vector > Reporters; + Reporters m_reporters; + +public: + void add( Ptr const& reporter ) { + m_reporters.push_back( reporter ); + } + +public: // IStreamingReporter + + virtual ReporterPreferences getPreferences() const CATCH_OVERRIDE { + return m_reporters[0]->getPreferences(); + } + + virtual void noMatchingTestCases( std::string const& spec ) CATCH_OVERRIDE { + for( Reporters::const_iterator it = m_reporters.begin(), itEnd = m_reporters.end(); + it != itEnd; + ++it ) + (*it)->noMatchingTestCases( spec ); + } + + virtual void testRunStarting( TestRunInfo const& testRunInfo ) CATCH_OVERRIDE { + for( Reporters::const_iterator it = m_reporters.begin(), itEnd = m_reporters.end(); + it != itEnd; + ++it ) + (*it)->testRunStarting( testRunInfo ); + } + + virtual void testGroupStarting( GroupInfo const& groupInfo ) CATCH_OVERRIDE { + for( Reporters::const_iterator it = m_reporters.begin(), itEnd = m_reporters.end(); + it != itEnd; + ++it ) + (*it)->testGroupStarting( groupInfo ); + } + + virtual void testCaseStarting( TestCaseInfo const& testInfo ) CATCH_OVERRIDE { + for( Reporters::const_iterator it = m_reporters.begin(), itEnd = m_reporters.end(); + it != itEnd; + ++it ) + (*it)->testCaseStarting( testInfo ); + } + + virtual void sectionStarting( SectionInfo const& sectionInfo ) CATCH_OVERRIDE { + for( Reporters::const_iterator it = m_reporters.begin(), itEnd = m_reporters.end(); + it != itEnd; + ++it ) + (*it)->sectionStarting( sectionInfo ); + } + + virtual void assertionStarting( AssertionInfo const& assertionInfo ) CATCH_OVERRIDE { + for( Reporters::const_iterator it = m_reporters.begin(), itEnd = m_reporters.end(); + it != itEnd; + ++it ) + (*it)->assertionStarting( assertionInfo ); + } + + // The return value indicates if the messages buffer should be cleared: + virtual bool assertionEnded( AssertionStats const& assertionStats ) CATCH_OVERRIDE { + bool clearBuffer = false; + for( Reporters::const_iterator it = m_reporters.begin(), itEnd = m_reporters.end(); + it != itEnd; + ++it ) + clearBuffer |= (*it)->assertionEnded( assertionStats ); + return clearBuffer; + } + + virtual void sectionEnded( SectionStats const& sectionStats ) CATCH_OVERRIDE { + for( Reporters::const_iterator it = m_reporters.begin(), itEnd = m_reporters.end(); + it != itEnd; + ++it ) + (*it)->sectionEnded( sectionStats ); + } + + virtual void testCaseEnded( TestCaseStats const& testCaseStats ) CATCH_OVERRIDE { + for( Reporters::const_iterator it = m_reporters.begin(), itEnd = m_reporters.end(); + it != itEnd; + ++it ) + (*it)->testCaseEnded( testCaseStats ); + } + + virtual void testGroupEnded( TestGroupStats const& testGroupStats ) CATCH_OVERRIDE { + for( Reporters::const_iterator it = m_reporters.begin(), itEnd = m_reporters.end(); + it != itEnd; + ++it ) + (*it)->testGroupEnded( testGroupStats ); + } + + virtual void testRunEnded( TestRunStats const& testRunStats ) CATCH_OVERRIDE { + for( Reporters::const_iterator it = m_reporters.begin(), itEnd = m_reporters.end(); + it != itEnd; + ++it ) + (*it)->testRunEnded( testRunStats ); + } + + virtual void skipTest( TestCaseInfo const& testInfo ) CATCH_OVERRIDE { + for( Reporters::const_iterator it = m_reporters.begin(), itEnd = m_reporters.end(); + it != itEnd; + ++it ) + (*it)->skipTest( testInfo ); + } +}; + +Ptr addReporter( Ptr const& existingReporter, Ptr const& additionalReporter ) { + Ptr resultingReporter; + + if( existingReporter ) { + MultipleReporters* multi = dynamic_cast( existingReporter.get() ); + if( !multi ) { + multi = new MultipleReporters; + resultingReporter = Ptr( multi ); + if( existingReporter ) + multi->add( existingReporter ); + } + else + resultingReporter = existingReporter; + multi->add( additionalReporter ); + } + else + resultingReporter = additionalReporter; + + return resultingReporter; +} + +} // end namespace Catch + // #included from: ../reporters/catch_reporter_xml.hpp #define TWOBLUECUBES_CATCH_REPORTER_XML_HPP_INCLUDED @@ -7591,47 +8289,53 @@ namespace Catch { StreamingReporterBase( ReporterConfig const& _config ) : m_config( _config.fullConfig() ), stream( _config.stream() ) - {} + { + m_reporterPrefs.shouldRedirectStdOut = false; + } - virtual ~StreamingReporterBase(); + virtual ReporterPreferences getPreferences() const CATCH_OVERRIDE { + return m_reporterPrefs; + } - virtual void noMatchingTestCases( std::string const& ) {} + virtual ~StreamingReporterBase() CATCH_OVERRIDE; - virtual void testRunStarting( TestRunInfo const& _testRunInfo ) { + virtual void noMatchingTestCases( std::string const& ) CATCH_OVERRIDE {} + + virtual void testRunStarting( TestRunInfo const& _testRunInfo ) CATCH_OVERRIDE { currentTestRunInfo = _testRunInfo; } - virtual void testGroupStarting( GroupInfo const& _groupInfo ) { + virtual void testGroupStarting( GroupInfo const& _groupInfo ) CATCH_OVERRIDE { currentGroupInfo = _groupInfo; } - virtual void testCaseStarting( TestCaseInfo const& _testInfo ) { + virtual void testCaseStarting( TestCaseInfo const& _testInfo ) CATCH_OVERRIDE { currentTestCaseInfo = _testInfo; } - virtual void sectionStarting( SectionInfo const& _sectionInfo ) { + virtual void sectionStarting( SectionInfo const& _sectionInfo ) CATCH_OVERRIDE { m_sectionStack.push_back( _sectionInfo ); } - virtual void sectionEnded( SectionStats const& /* _sectionStats */ ) { + virtual void sectionEnded( SectionStats const& /* _sectionStats */ ) CATCH_OVERRIDE { m_sectionStack.pop_back(); } - virtual void testCaseEnded( TestCaseStats const& /* _testCaseStats */ ) { + virtual void testCaseEnded( TestCaseStats const& /* _testCaseStats */ ) CATCH_OVERRIDE { currentTestCaseInfo.reset(); } - virtual void testGroupEnded( TestGroupStats const& /* _testGroupStats */ ) { + virtual void testGroupEnded( TestGroupStats const& /* _testGroupStats */ ) CATCH_OVERRIDE { currentGroupInfo.reset(); } - virtual void testRunEnded( TestRunStats const& /* _testRunStats */ ) { + virtual void testRunEnded( TestRunStats const& /* _testRunStats */ ) CATCH_OVERRIDE { currentTestCaseInfo.reset(); currentGroupInfo.reset(); currentTestRunInfo.reset(); } - virtual void skipTest( TestCaseInfo const& ) { + virtual void skipTest( TestCaseInfo const& ) CATCH_OVERRIDE { // Don't do anything with this by default. // It can optionally be overridden in the derived class. } - Ptr m_config; + Ptr m_config; std::ostream& stream; LazyStat currentTestRunInfo; @@ -7639,6 +8343,7 @@ namespace Catch { LazyStat currentTestCaseInfo; std::vector m_sectionStack; + ReporterPreferences m_reporterPrefs; }; struct CumulativeReporterBase : SharedImpl { @@ -7689,15 +8394,21 @@ namespace Catch { CumulativeReporterBase( ReporterConfig const& _config ) : m_config( _config.fullConfig() ), stream( _config.stream() ) - {} + { + m_reporterPrefs.shouldRedirectStdOut = false; + } ~CumulativeReporterBase(); - virtual void testRunStarting( TestRunInfo const& ) {} - virtual void testGroupStarting( GroupInfo const& ) {} + virtual ReporterPreferences getPreferences() const CATCH_OVERRIDE { + return m_reporterPrefs; + } - virtual void testCaseStarting( TestCaseInfo const& ) {} + virtual void testRunStarting( TestRunInfo const& ) CATCH_OVERRIDE {} + virtual void testGroupStarting( GroupInfo const& ) CATCH_OVERRIDE {} - virtual void sectionStarting( SectionInfo const& sectionInfo ) { + virtual void testCaseStarting( TestCaseInfo const& ) CATCH_OVERRIDE {} + + virtual void sectionStarting( SectionInfo const& sectionInfo ) CATCH_OVERRIDE { SectionStats incompleteStats( sectionInfo, Counts(), 0, false ); Ptr node; if( m_sectionStack.empty() ) { @@ -7722,7 +8433,7 @@ namespace Catch { m_deepestSection = node; } - virtual void assertionStarting( AssertionInfo const& ) {} + virtual void assertionStarting( AssertionInfo const& ) CATCH_OVERRIDE {} virtual bool assertionEnded( AssertionStats const& assertionStats ) { assert( !m_sectionStack.empty() ); @@ -7730,13 +8441,13 @@ namespace Catch { sectionNode.assertions.push_back( assertionStats ); return true; } - virtual void sectionEnded( SectionStats const& sectionStats ) { + virtual void sectionEnded( SectionStats const& sectionStats ) CATCH_OVERRIDE { assert( !m_sectionStack.empty() ); SectionNode& node = *m_sectionStack.back(); node.stats = sectionStats; m_sectionStack.pop_back(); } - virtual void testCaseEnded( TestCaseStats const& testCaseStats ) { + virtual void testCaseEnded( TestCaseStats const& testCaseStats ) CATCH_OVERRIDE { Ptr node = new TestCaseNode( testCaseStats ); assert( m_sectionStack.size() == 0 ); node->children.push_back( m_rootSection ); @@ -7747,12 +8458,12 @@ namespace Catch { m_deepestSection->stdOut = testCaseStats.stdOut; m_deepestSection->stdErr = testCaseStats.stdErr; } - virtual void testGroupEnded( TestGroupStats const& testGroupStats ) { + virtual void testGroupEnded( TestGroupStats const& testGroupStats ) CATCH_OVERRIDE { Ptr node = new TestGroupNode( testGroupStats ); node->children.swap( m_testCases ); m_testGroups.push_back( node ); } - virtual void testRunEnded( TestRunStats const& testRunStats ) { + virtual void testRunEnded( TestRunStats const& testRunStats ) CATCH_OVERRIDE { Ptr node = new TestRunNode( testRunStats ); node->children.swap( m_testGroups ); m_testRuns.push_back( node ); @@ -7760,9 +8471,9 @@ namespace Catch { } virtual void testRunEndedCumulative() = 0; - virtual void skipTest( TestCaseInfo const& ) {} + virtual void skipTest( TestCaseInfo const& ) CATCH_OVERRIDE {} - Ptr m_config; + Ptr m_config; std::ostream& stream; std::vector m_assertions; std::vector > > m_sections; @@ -7774,6 +8485,7 @@ namespace Catch { Ptr m_rootSection; Ptr m_deepestSection; std::vector > m_sectionStack; + ReporterPreferences m_reporterPrefs; }; @@ -7787,6 +8499,17 @@ namespace Catch { return line; } + struct TestEventListenerBase : StreamingReporterBase { + TestEventListenerBase( ReporterConfig const& _config ) + : StreamingReporterBase( _config ) + {} + + virtual void assertionStarting( AssertionInfo const& ) CATCH_OVERRIDE {} + virtual bool assertionEnded( AssertionStats const& ) CATCH_OVERRIDE { + return false; + } + }; + } // end namespace Catch // #included from: ../internal/catch_reporter_registrars.hpp @@ -7817,7 +8540,7 @@ namespace Catch { template class ReporterRegistrar { - class ReporterFactory : public IReporterFactory { + class ReporterFactory : public SharedImpl { // *** Please Note ***: // - If you end up here looking at a compiler error because it's trying to register @@ -7845,22 +8568,102 @@ namespace Catch { getMutableRegistryHub().registerReporter( name, new ReporterFactory() ); } }; + + template + class ListenerRegistrar { + + class ListenerFactory : public SharedImpl { + + virtual IStreamingReporter* create( ReporterConfig const& config ) const { + return new T( config ); + } + virtual std::string getDescription() const { + return ""; + } + }; + + public: + + ListenerRegistrar() { + getMutableRegistryHub().registerListener( new ListenerFactory() ); + } + }; } #define INTERNAL_CATCH_REGISTER_LEGACY_REPORTER( name, reporterType ) \ namespace{ Catch::LegacyReporterRegistrar catch_internal_RegistrarFor##reporterType( name ); } + #define INTERNAL_CATCH_REGISTER_REPORTER( name, reporterType ) \ namespace{ Catch::ReporterRegistrar catch_internal_RegistrarFor##reporterType( name ); } +#define INTERNAL_CATCH_REGISTER_LISTENER( listenerType ) \ + namespace{ Catch::ListenerRegistrar catch_internal_RegistrarFor##listenerType; } + // #included from: ../internal/catch_xmlwriter.hpp #define TWOBLUECUBES_CATCH_XMLWRITER_HPP_INCLUDED #include #include #include +#include namespace Catch { + class XmlEncode { + public: + enum ForWhat { ForTextNodes, ForAttributes }; + + XmlEncode( std::string const& str, ForWhat forWhat = ForTextNodes ) + : m_str( str ), + m_forWhat( forWhat ) + {} + + void encodeTo( std::ostream& os ) const { + + // Apostrophe escaping not necessary if we always use " to write attributes + // (see: http://www.w3.org/TR/xml/#syntax) + + for( std::size_t i = 0; i < m_str.size(); ++ i ) { + char c = m_str[i]; + switch( c ) { + case '<': os << "<"; break; + case '&': os << "&"; break; + + case '>': + // See: http://www.w3.org/TR/xml/#syntax + if( i > 2 && m_str[i-1] == ']' && m_str[i-2] == ']' ) + os << ">"; + else + os << c; + break; + + case '\"': + if( m_forWhat == ForAttributes ) + os << """; + else + os << c; + break; + + default: + // Escape control chars - based on contribution by @espenalb in PR #465 + if ( ( c < '\x09' ) || ( c > '\x0D' && c < '\x20') || c=='\x7F' ) + os << "&#x" << std::uppercase << std::hex << static_cast( c ); + else + os << c; + } + } + } + + friend std::ostream& operator << ( std::ostream& os, XmlEncode const& xmlEncode ) { + xmlEncode.encodeTo( os ); + return os; + } + + private: + std::string m_str; + ForWhat m_forWhat; + }; + class XmlWriter { public: @@ -7872,7 +8675,7 @@ namespace Catch { ScopedElement( ScopedElement const& other ) : m_writer( other.m_writer ){ - other.m_writer = NULL; + other.m_writer = CATCH_NULL; } ~ScopedElement() { @@ -7943,11 +8746,8 @@ namespace Catch { } XmlWriter& writeAttribute( std::string const& name, std::string const& attribute ) { - if( !name.empty() && !attribute.empty() ) { - stream() << " " << name << "=\""; - writeEncodedText( attribute ); - stream() << "\""; - } + if( !name.empty() && !attribute.empty() ) + stream() << " " << name << "=\"" << XmlEncode( attribute, XmlEncode::ForAttributes ) << "\""; return *this; } @@ -7958,9 +8758,9 @@ namespace Catch { template XmlWriter& writeAttribute( std::string const& name, T const& attribute ) { - if( !name.empty() ) - stream() << " " << name << "=\"" << attribute << "\""; - return *this; + std::ostringstream oss; + oss << attribute; + return writeAttribute( name, oss.str() ); } XmlWriter& writeText( std::string const& text, bool indent = true ) { @@ -7969,7 +8769,7 @@ namespace Catch { ensureTagClosed(); if( tagWasOpen && indent ) stream() << m_indent; - writeEncodedText( text ); + stream() << XmlEncode( text ); m_needsNewline = true; } return *this; @@ -8014,30 +8814,6 @@ namespace Catch { } } - void writeEncodedText( std::string const& text ) { - static const char* charsToEncode = "<&\""; - std::string mtext = text; - std::string::size_type pos = mtext.find_first_of( charsToEncode ); - while( pos != std::string::npos ) { - stream() << mtext.substr( 0, pos ); - - switch( mtext[pos] ) { - case '<': - stream() << "<"; - break; - case '&': - stream() << "&"; - break; - case '\"': - stream() << """; - break; - } - mtext = mtext.substr( pos+1 ); - pos = mtext.find_first_of( charsToEncode ); - } - stream() << mtext; - } - bool m_tagIsOpen; bool m_needsNewline; std::vector m_tags; @@ -8046,32 +8822,44 @@ namespace Catch { }; } +// #included from: catch_reenable_warnings.h + +#define TWOBLUECUBES_CATCH_REENABLE_WARNINGS_H_INCLUDED + +#ifdef __clang__ +# ifdef __ICC // icpc defines the __clang__ macro +# pragma warning(pop) +# else +# pragma clang diagnostic pop +# endif +#elif defined __GNUC__ +# pragma GCC diagnostic pop +#endif + + namespace Catch { class XmlReporter : public StreamingReporterBase { public: XmlReporter( ReporterConfig const& _config ) : StreamingReporterBase( _config ), m_sectionDepth( 0 ) - {} + { + m_reporterPrefs.shouldRedirectStdOut = true; + } - virtual ~XmlReporter(); + virtual ~XmlReporter() CATCH_OVERRIDE; static std::string getDescription() { return "Reports test results as an XML document"; } public: // StreamingReporterBase - virtual ReporterPreferences getPreferences() const { - ReporterPreferences prefs; - prefs.shouldRedirectStdOut = true; - return prefs; - } - virtual void noMatchingTestCases( std::string const& s ) { + virtual void noMatchingTestCases( std::string const& s ) CATCH_OVERRIDE { StreamingReporterBase::noMatchingTestCases( s ); } - virtual void testRunStarting( TestRunInfo const& testInfo ) { + virtual void testRunStarting( TestRunInfo const& testInfo ) CATCH_OVERRIDE { StreamingReporterBase::testRunStarting( testInfo ); m_xml.setStream( stream ); m_xml.startElement( "Catch" ); @@ -8079,13 +8867,13 @@ namespace Catch { m_xml.writeAttribute( "name", m_config->name() ); } - virtual void testGroupStarting( GroupInfo const& groupInfo ) { + virtual void testGroupStarting( GroupInfo const& groupInfo ) CATCH_OVERRIDE { StreamingReporterBase::testGroupStarting( groupInfo ); m_xml.startElement( "Group" ) .writeAttribute( "name", groupInfo.name ); } - virtual void testCaseStarting( TestCaseInfo const& testInfo ) { + virtual void testCaseStarting( TestCaseInfo const& testInfo ) CATCH_OVERRIDE { StreamingReporterBase::testCaseStarting(testInfo); m_xml.startElement( "TestCase" ).writeAttribute( "name", trim( testInfo.name ) ); @@ -8093,7 +8881,7 @@ namespace Catch { m_testCaseTimer.start(); } - virtual void sectionStarting( SectionInfo const& sectionInfo ) { + virtual void sectionStarting( SectionInfo const& sectionInfo ) CATCH_OVERRIDE { StreamingReporterBase::sectionStarting( sectionInfo ); if( m_sectionDepth++ > 0 ) { m_xml.startElement( "Section" ) @@ -8102,9 +8890,9 @@ namespace Catch { } } - virtual void assertionStarting( AssertionInfo const& ) { } + virtual void assertionStarting( AssertionInfo const& ) CATCH_OVERRIDE { } - virtual bool assertionEnded( AssertionStats const& assertionStats ) { + virtual bool assertionEnded( AssertionStats const& assertionStats ) CATCH_OVERRIDE { const AssertionResult& assertionResult = assertionStats.assertionResult; // Print any info messages in tags. @@ -8175,7 +8963,7 @@ namespace Catch { return true; } - virtual void sectionEnded( SectionStats const& sectionStats ) { + virtual void sectionEnded( SectionStats const& sectionStats ) CATCH_OVERRIDE { StreamingReporterBase::sectionEnded( sectionStats ); if( --m_sectionDepth > 0 ) { XmlWriter::ScopedElement e = m_xml.scopedElement( "OverallResults" ); @@ -8190,7 +8978,7 @@ namespace Catch { } } - virtual void testCaseEnded( TestCaseStats const& testCaseStats ) { + virtual void testCaseEnded( TestCaseStats const& testCaseStats ) CATCH_OVERRIDE { StreamingReporterBase::testCaseEnded( testCaseStats ); XmlWriter::ScopedElement e = m_xml.scopedElement( "OverallResult" ); e.writeAttribute( "success", testCaseStats.totals.assertions.allOk() ); @@ -8201,7 +8989,7 @@ namespace Catch { m_xml.endElement(); } - virtual void testGroupEnded( TestGroupStats const& testGroupStats ) { + virtual void testGroupEnded( TestGroupStats const& testGroupStats ) CATCH_OVERRIDE { StreamingReporterBase::testGroupEnded( testGroupStats ); // TODO: Check testGroupStats.aborting and act accordingly. m_xml.scopedElement( "OverallResults" ) @@ -8211,7 +8999,7 @@ namespace Catch { m_xml.endElement(); } - virtual void testRunEnded( TestRunStats const& testRunStats ) { + virtual void testRunEnded( TestRunStats const& testRunStats ) CATCH_OVERRIDE { StreamingReporterBase::testRunEnded( testRunStats ); m_xml.scopedElement( "OverallResults" ) .writeAttribute( "successes", testRunStats.totals.assertions.passed ) @@ -8242,28 +9030,24 @@ namespace Catch { JunitReporter( ReporterConfig const& _config ) : CumulativeReporterBase( _config ), xml( _config.stream() ) - {} + { + m_reporterPrefs.shouldRedirectStdOut = true; + } - ~JunitReporter(); + virtual ~JunitReporter() CATCH_OVERRIDE; static std::string getDescription() { return "Reports test results in an XML format that looks like Ant's junitreport target"; } - virtual void noMatchingTestCases( std::string const& /*spec*/ ) {} + virtual void noMatchingTestCases( std::string const& /*spec*/ ) CATCH_OVERRIDE {} - virtual ReporterPreferences getPreferences() const { - ReporterPreferences prefs; - prefs.shouldRedirectStdOut = true; - return prefs; - } - - virtual void testRunStarting( TestRunInfo const& runInfo ) { + virtual void testRunStarting( TestRunInfo const& runInfo ) CATCH_OVERRIDE { CumulativeReporterBase::testRunStarting( runInfo ); xml.startElement( "testsuites" ); } - virtual void testGroupStarting( GroupInfo const& groupInfo ) { + virtual void testGroupStarting( GroupInfo const& groupInfo ) CATCH_OVERRIDE { suiteTimer.start(); stdOutForSuite.str(""); stdErrForSuite.str(""); @@ -8271,25 +9055,25 @@ namespace Catch { CumulativeReporterBase::testGroupStarting( groupInfo ); } - virtual bool assertionEnded( AssertionStats const& assertionStats ) { + virtual bool assertionEnded( AssertionStats const& assertionStats ) CATCH_OVERRIDE { if( assertionStats.assertionResult.getResultType() == ResultWas::ThrewException ) unexpectedExceptions++; return CumulativeReporterBase::assertionEnded( assertionStats ); } - virtual void testCaseEnded( TestCaseStats const& testCaseStats ) { + virtual void testCaseEnded( TestCaseStats const& testCaseStats ) CATCH_OVERRIDE { stdOutForSuite << testCaseStats.stdOut; stdErrForSuite << testCaseStats.stdErr; CumulativeReporterBase::testCaseEnded( testCaseStats ); } - virtual void testGroupEnded( TestGroupStats const& testGroupStats ) { + virtual void testGroupEnded( TestGroupStats const& testGroupStats ) CATCH_OVERRIDE { double suiteTime = suiteTimer.getElapsedSeconds(); CumulativeReporterBase::testGroupEnded( testGroupStats ); writeGroup( *m_testGroups.back(), suiteTime ); } - virtual void testRunEndedCumulative() { + virtual void testRunEndedCumulative() CATCH_OVERRIDE { xml.endElement(); } @@ -8454,24 +9238,19 @@ namespace Catch { m_headerPrinted( false ) {} - virtual ~ConsoleReporter(); + virtual ~ConsoleReporter() CATCH_OVERRIDE; static std::string getDescription() { return "Reports test results as plain lines of text"; } - virtual ReporterPreferences getPreferences() const { - ReporterPreferences prefs; - prefs.shouldRedirectStdOut = false; - return prefs; - } - virtual void noMatchingTestCases( std::string const& spec ) { + virtual void noMatchingTestCases( std::string const& spec ) CATCH_OVERRIDE { stream << "No test cases matched '" << spec << "'" << std::endl; } - virtual void assertionStarting( AssertionInfo const& ) { + virtual void assertionStarting( AssertionInfo const& ) CATCH_OVERRIDE { } - virtual bool assertionEnded( AssertionStats const& _assertionStats ) { + virtual bool assertionEnded( AssertionStats const& _assertionStats ) CATCH_OVERRIDE { AssertionResult const& result = _assertionStats.assertionResult; bool printInfoMessages = true; @@ -8491,11 +9270,11 @@ namespace Catch { return true; } - virtual void sectionStarting( SectionInfo const& _sectionInfo ) { + virtual void sectionStarting( SectionInfo const& _sectionInfo ) CATCH_OVERRIDE { m_headerPrinted = false; StreamingReporterBase::sectionStarting( _sectionInfo ); } - virtual void sectionEnded( SectionStats const& _sectionStats ) { + virtual void sectionEnded( SectionStats const& _sectionStats ) CATCH_OVERRIDE { if( _sectionStats.missingAssertions ) { lazyPrint(); Colour colour( Colour::ResultError ); @@ -8517,11 +9296,11 @@ namespace Catch { StreamingReporterBase::sectionEnded( _sectionStats ); } - virtual void testCaseEnded( TestCaseStats const& _testCaseStats ) { + virtual void testCaseEnded( TestCaseStats const& _testCaseStats ) CATCH_OVERRIDE { StreamingReporterBase::testCaseEnded( _testCaseStats ); m_headerPrinted = false; } - virtual void testGroupEnded( TestGroupStats const& _testGroupStats ) { + virtual void testGroupEnded( TestGroupStats const& _testGroupStats ) CATCH_OVERRIDE { if( currentGroupInfo.used ) { printSummaryDivider(); stream << "Summary for group '" << _testGroupStats.groupInfo.name << "':\n"; @@ -8530,7 +9309,7 @@ namespace Catch { } StreamingReporterBase::testGroupEnded( _testGroupStats ); } - virtual void testRunEnded( TestRunStats const& _testRunStats ) { + virtual void testRunEnded( TestRunStats const& _testRunStats ) CATCH_OVERRIDE { printTotalsDivider( _testRunStats.totals ); printTotals( _testRunStats.totals ); stream << std::endl; @@ -9169,8 +9948,14 @@ namespace Catch { } // end namespace Catch namespace Catch { + // These are all here to avoid warnings about not having any out of line + // virtual methods NonCopyable::~NonCopyable() {} IShared::~IShared() {} + IStream::~IStream() CATCH_NOEXCEPT {} + FileStream::~FileStream() CATCH_NOEXCEPT {} + CoutStream::~CoutStream() CATCH_NOEXCEPT {} + DebugOutStream::~DebugOutStream() CATCH_NOEXCEPT {} StreamBufBase::~StreamBufBase() CATCH_NOEXCEPT {} IContext::~IContext() {} IResultCapture::~IResultCapture() {} @@ -9204,6 +9989,7 @@ namespace Catch { FreeFunctionTestCase::~FreeFunctionTestCase() {} IGeneratorInfo::~IGeneratorInfo() {} IGeneratorsForTest::~IGeneratorsForTest() {} + WildcardPattern::~WildcardPattern() {} TestSpec::Pattern::~Pattern() {} TestSpec::NamePattern::~NamePattern() {} TestSpec::TagPattern::~TagPattern() {} @@ -9215,6 +10001,13 @@ namespace Catch { Matchers::Impl::StdString::EndsWith::~EndsWith() {} void Config::dummy() {} + + namespace TestCaseTracking { + ITracker::~ITracker() {} + TrackerBase::~TrackerBase() {} + SectionTracker::~SectionTracker() {} + IndexTracker::~IndexTracker() {} + } } #ifdef __clang__ @@ -9230,7 +10023,7 @@ namespace Catch { #ifndef __OBJC__ // Standard C/C++ main entry point -int main (int argc, char * const argv[]) { +int main (int argc, char * argv[]) { return Catch::Session().run( argc, argv ); } @@ -9268,8 +10061,9 @@ int main (int argc, char * const argv[]) { #define CATCH_REQUIRE( expr ) INTERNAL_CATCH_TEST( expr, Catch::ResultDisposition::Normal, "CATCH_REQUIRE" ) #define CATCH_REQUIRE_FALSE( expr ) INTERNAL_CATCH_TEST( expr, Catch::ResultDisposition::Normal | Catch::ResultDisposition::FalseTest, "CATCH_REQUIRE_FALSE" ) -#define CATCH_REQUIRE_THROWS( expr ) INTERNAL_CATCH_THROWS( expr, Catch::ResultDisposition::Normal, "CATCH_REQUIRE_THROWS" ) +#define CATCH_REQUIRE_THROWS( expr ) INTERNAL_CATCH_THROWS( expr, Catch::ResultDisposition::Normal, "", "CATCH_REQUIRE_THROWS" ) #define CATCH_REQUIRE_THROWS_AS( expr, exceptionType ) INTERNAL_CATCH_THROWS_AS( expr, exceptionType, Catch::ResultDisposition::Normal, "CATCH_REQUIRE_THROWS_AS" ) +#define CATCH_REQUIRE_THROWS_WITH( expr, matcher ) INTERNAL_CATCH_THROWS( expr, Catch::ResultDisposition::Normal, matcher, "CATCH_REQUIRE_THROWS_WITH" ) #define CATCH_REQUIRE_NOTHROW( expr ) INTERNAL_CATCH_NO_THROW( expr, Catch::ResultDisposition::Normal, "CATCH_REQUIRE_NOTHROW" ) #define CATCH_CHECK( expr ) INTERNAL_CATCH_TEST( expr, Catch::ResultDisposition::ContinueOnFailure, "CATCH_CHECK" ) @@ -9280,6 +10074,7 @@ int main (int argc, char * const argv[]) { #define CATCH_CHECK_THROWS( expr ) INTERNAL_CATCH_THROWS( expr, Catch::ResultDisposition::ContinueOnFailure, "CATCH_CHECK_THROWS" ) #define CATCH_CHECK_THROWS_AS( expr, exceptionType ) INTERNAL_CATCH_THROWS_AS( expr, exceptionType, Catch::ResultDisposition::ContinueOnFailure, "CATCH_CHECK_THROWS_AS" ) +#define CATCH_CHECK_THROWS_WITH( expr, matcher ) INTERNAL_CATCH_THROWS( expr, Catch::ResultDisposition::ContinueOnFailure, matcher, "CATCH_CHECK_THROWS_WITH" ) #define CATCH_CHECK_NOTHROW( expr ) INTERNAL_CATCH_NO_THROW( expr, Catch::ResultDisposition::ContinueOnFailure, "CATCH_CHECK_NOTHROW" ) #define CHECK_THAT( arg, matcher ) INTERNAL_CHECK_THAT( arg, matcher, Catch::ResultDisposition::ContinueOnFailure, "CATCH_CHECK_THAT" ) @@ -9295,6 +10090,7 @@ int main (int argc, char * const argv[]) { #define CATCH_TEST_CASE( ... ) INTERNAL_CATCH_TESTCASE( __VA_ARGS__ ) #define CATCH_TEST_CASE_METHOD( className, ... ) INTERNAL_CATCH_TEST_CASE_METHOD( className, __VA_ARGS__ ) #define CATCH_METHOD_AS_TEST_CASE( method, ... ) INTERNAL_CATCH_METHOD_AS_TEST_CASE( method, __VA_ARGS__ ) + #define CATCH_REGISTER_TEST_CASE( ... ) INTERNAL_CATCH_REGISTER_TESTCASE( __VA_ARGS__ ) #define CATCH_SECTION( ... ) INTERNAL_CATCH_SECTION( __VA_ARGS__ ) #define CATCH_FAIL( ... ) INTERNAL_CATCH_MSG( Catch::ResultWas::ExplicitFailure, Catch::ResultDisposition::Normal, "CATCH_FAIL", __VA_ARGS__ ) #define CATCH_SUCCEED( ... ) INTERNAL_CATCH_MSG( Catch::ResultWas::Ok, Catch::ResultDisposition::ContinueOnFailure, "CATCH_SUCCEED", __VA_ARGS__ ) @@ -9302,6 +10098,7 @@ int main (int argc, char * const argv[]) { #define CATCH_TEST_CASE( name, description ) INTERNAL_CATCH_TESTCASE( name, description ) #define CATCH_TEST_CASE_METHOD( className, name, description ) INTERNAL_CATCH_TEST_CASE_METHOD( className, name, description ) #define CATCH_METHOD_AS_TEST_CASE( method, name, description ) INTERNAL_CATCH_METHOD_AS_TEST_CASE( method, name, description ) + #define CATCH_REGISTER_TEST_CASE( function, name, description ) INTERNAL_CATCH_REGISTER_TESTCASE( function, name, description ) #define CATCH_SECTION( name, description ) INTERNAL_CATCH_SECTION( name, description ) #define CATCH_FAIL( msg ) INTERNAL_CATCH_MSG( Catch::ResultWas::ExplicitFailure, Catch::ResultDisposition::Normal, "CATCH_FAIL", msg ) #define CATCH_SUCCEED( msg ) INTERNAL_CATCH_MSG( Catch::ResultWas::Ok, Catch::ResultDisposition::ContinueOnFailure, "CATCH_SUCCEED", msg ) @@ -9321,11 +10118,11 @@ int main (int argc, char * const argv[]) { #define CATCH_SCENARIO( name, tags ) CATCH_TEST_CASE( "Scenario: " name, tags ) #define CATCH_SCENARIO_METHOD( className, name, tags ) INTERNAL_CATCH_TEST_CASE_METHOD( className, "Scenario: " name, tags ) #endif -#define CATCH_GIVEN( desc ) CATCH_SECTION( "Given: " desc, "" ) -#define CATCH_WHEN( desc ) CATCH_SECTION( " When: " desc, "" ) -#define CATCH_AND_WHEN( desc ) CATCH_SECTION( " And: " desc, "" ) -#define CATCH_THEN( desc ) CATCH_SECTION( " Then: " desc, "" ) -#define CATCH_AND_THEN( desc ) CATCH_SECTION( " And: " desc, "" ) +#define CATCH_GIVEN( desc ) CATCH_SECTION( std::string( "Given: ") + desc, "" ) +#define CATCH_WHEN( desc ) CATCH_SECTION( std::string( " When: ") + desc, "" ) +#define CATCH_AND_WHEN( desc ) CATCH_SECTION( std::string( " And: ") + desc, "" ) +#define CATCH_THEN( desc ) CATCH_SECTION( std::string( " Then: ") + desc, "" ) +#define CATCH_AND_THEN( desc ) CATCH_SECTION( std::string( " And: ") + desc, "" ) // If CATCH_CONFIG_PREFIX_ALL is not defined then the CATCH_ prefix is not required #else @@ -9333,8 +10130,9 @@ int main (int argc, char * const argv[]) { #define REQUIRE( expr ) INTERNAL_CATCH_TEST( expr, Catch::ResultDisposition::Normal, "REQUIRE" ) #define REQUIRE_FALSE( expr ) INTERNAL_CATCH_TEST( expr, Catch::ResultDisposition::Normal | Catch::ResultDisposition::FalseTest, "REQUIRE_FALSE" ) -#define REQUIRE_THROWS( expr ) INTERNAL_CATCH_THROWS( expr, Catch::ResultDisposition::Normal, "REQUIRE_THROWS" ) +#define REQUIRE_THROWS( expr ) INTERNAL_CATCH_THROWS( expr, Catch::ResultDisposition::Normal, "", "REQUIRE_THROWS" ) #define REQUIRE_THROWS_AS( expr, exceptionType ) INTERNAL_CATCH_THROWS_AS( expr, exceptionType, Catch::ResultDisposition::Normal, "REQUIRE_THROWS_AS" ) +#define REQUIRE_THROWS_WITH( expr, matcher ) INTERNAL_CATCH_THROWS( expr, Catch::ResultDisposition::Normal, matcher, "REQUIRE_THROWS_WITH" ) #define REQUIRE_NOTHROW( expr ) INTERNAL_CATCH_NO_THROW( expr, Catch::ResultDisposition::Normal, "REQUIRE_NOTHROW" ) #define CHECK( expr ) INTERNAL_CATCH_TEST( expr, Catch::ResultDisposition::ContinueOnFailure, "CHECK" ) @@ -9343,8 +10141,9 @@ int main (int argc, char * const argv[]) { #define CHECKED_ELSE( expr ) INTERNAL_CATCH_ELSE( expr, Catch::ResultDisposition::ContinueOnFailure, "CHECKED_ELSE" ) #define CHECK_NOFAIL( expr ) INTERNAL_CATCH_TEST( expr, Catch::ResultDisposition::ContinueOnFailure | Catch::ResultDisposition::SuppressFail, "CHECK_NOFAIL" ) -#define CHECK_THROWS( expr ) INTERNAL_CATCH_THROWS( expr, Catch::ResultDisposition::ContinueOnFailure, "CHECK_THROWS" ) +#define CHECK_THROWS( expr ) INTERNAL_CATCH_THROWS( expr, Catch::ResultDisposition::ContinueOnFailure, "", "CHECK_THROWS" ) #define CHECK_THROWS_AS( expr, exceptionType ) INTERNAL_CATCH_THROWS_AS( expr, exceptionType, Catch::ResultDisposition::ContinueOnFailure, "CHECK_THROWS_AS" ) +#define CHECK_THROWS_WITH( expr, matcher ) INTERNAL_CATCH_THROWS( expr, Catch::ResultDisposition::ContinueOnFailure, matcher, "CHECK_THROWS_WITH" ) #define CHECK_NOTHROW( expr ) INTERNAL_CATCH_NO_THROW( expr, Catch::ResultDisposition::ContinueOnFailure, "CHECK_NOTHROW" ) #define CHECK_THAT( arg, matcher ) INTERNAL_CHECK_THAT( arg, matcher, Catch::ResultDisposition::ContinueOnFailure, "CHECK_THAT" ) @@ -9360,6 +10159,7 @@ int main (int argc, char * const argv[]) { #define TEST_CASE( ... ) INTERNAL_CATCH_TESTCASE( __VA_ARGS__ ) #define TEST_CASE_METHOD( className, ... ) INTERNAL_CATCH_TEST_CASE_METHOD( className, __VA_ARGS__ ) #define METHOD_AS_TEST_CASE( method, ... ) INTERNAL_CATCH_METHOD_AS_TEST_CASE( method, __VA_ARGS__ ) + #define REGISTER_TEST_CASE( ... ) INTERNAL_CATCH_REGISTER_TESTCASE( __VA_ARGS__ ) #define SECTION( ... ) INTERNAL_CATCH_SECTION( __VA_ARGS__ ) #define FAIL( ... ) INTERNAL_CATCH_MSG( Catch::ResultWas::ExplicitFailure, Catch::ResultDisposition::Normal, "FAIL", __VA_ARGS__ ) #define SUCCEED( ... ) INTERNAL_CATCH_MSG( Catch::ResultWas::Ok, Catch::ResultDisposition::ContinueOnFailure, "SUCCEED", __VA_ARGS__ ) @@ -9367,6 +10167,7 @@ int main (int argc, char * const argv[]) { #define TEST_CASE( name, description ) INTERNAL_CATCH_TESTCASE( name, description ) #define TEST_CASE_METHOD( className, name, description ) INTERNAL_CATCH_TEST_CASE_METHOD( className, name, description ) #define METHOD_AS_TEST_CASE( method, name, description ) INTERNAL_CATCH_METHOD_AS_TEST_CASE( method, name, description ) + #define REGISTER_TEST_CASE( ... ) INTERNAL_CATCH_REGISTER_TESTCASE( __VA_ARGS__ ) #define SECTION( name, description ) INTERNAL_CATCH_SECTION( name, description ) #define FAIL( msg ) INTERNAL_CATCH_MSG( Catch::ResultWas::ExplicitFailure, Catch::ResultDisposition::Normal, "FAIL", msg ) #define SUCCEED( msg ) INTERNAL_CATCH_MSG( Catch::ResultWas::Ok, Catch::ResultDisposition::ContinueOnFailure, "SUCCEED", msg ) @@ -9390,27 +10191,13 @@ int main (int argc, char * const argv[]) { #define SCENARIO( name, tags ) TEST_CASE( "Scenario: " name, tags ) #define SCENARIO_METHOD( className, name, tags ) INTERNAL_CATCH_TEST_CASE_METHOD( className, "Scenario: " name, tags ) #endif -#define GIVEN( desc ) SECTION( " Given: " desc, "" ) -#define WHEN( desc ) SECTION( " When: " desc, "" ) -#define AND_WHEN( desc ) SECTION( "And when: " desc, "" ) -#define THEN( desc ) SECTION( " Then: " desc, "" ) -#define AND_THEN( desc ) SECTION( " And: " desc, "" ) +#define GIVEN( desc ) SECTION( std::string(" Given: ") + desc, "" ) +#define WHEN( desc ) SECTION( std::string(" When: ") + desc, "" ) +#define AND_WHEN( desc ) SECTION( std::string("And when: ") + desc, "" ) +#define THEN( desc ) SECTION( std::string(" Then: ") + desc, "" ) +#define AND_THEN( desc ) SECTION( std::string(" And: ") + desc, "" ) using Catch::Detail::Approx; -// #included from: internal/catch_reenable_warnings.h - -#define TWOBLUECUBES_CATCH_REENABLE_WARNINGS_H_INCLUDED - -#ifdef __clang__ -# ifdef __ICC // icpc defines the __clang__ macro -# pragma warning(pop) -# else -# pragma clang diagnostic pop -# endif -#elif defined __GNUC__ -# pragma GCC diagnostic pop -#endif - #endif // TWOBLUECUBES_SINGLE_INCLUDE_CATCH_HPP_INCLUDED diff --git a/test/json_nlohmann_tests/bom.json b/test/json_nlohmann_tests/bom.json new file mode 100644 index 000000000..9d02f600b --- /dev/null +++ b/test/json_nlohmann_tests/bom.json @@ -0,0 +1,3 @@ +{ + "foo": true +} diff --git a/test/unit.cpp b/test/unit.cpp index 379b15724..81b341f69 100644 --- a/test/unit.cpp +++ b/test/unit.cpp @@ -895,6 +895,8 @@ TEST_CASE("constructors") SECTION("object with error") { CHECK_THROWS_AS(json::object({ {"one", 1}, {"two", 2.2}, {"three", false}, 13 }), std::logic_error); + CHECK_THROWS_WITH(json::object({ {"one", 1}, {"two", 2.2}, {"three", false}, 13 }), + "cannot create object from initializer list"); } SECTION("empty array") @@ -968,12 +970,16 @@ TEST_CASE("constructors") json jobject2 = {{"a", "a"}, {"b", 1}, {"c", 17}}; CHECK_THROWS_AS(json(jobject.begin(), jobject2.end()), std::domain_error); CHECK_THROWS_AS(json(jobject2.begin(), jobject.end()), std::domain_error); + CHECK_THROWS_WITH(json(jobject.begin(), jobject2.end()), "iterators are not compatible"); + CHECK_THROWS_WITH(json(jobject2.begin(), jobject.end()), "iterators are not compatible"); } { json jobject = {{"a", "a"}, {"b", 1}, {"c", 17}, {"d", false}, {"e", true}}; json jobject2 = {{"a", "a"}, {"b", 1}, {"c", 17}}; CHECK_THROWS_AS(json(jobject.cbegin(), jobject2.cend()), std::domain_error); CHECK_THROWS_AS(json(jobject2.cbegin(), jobject.cend()), std::domain_error); + CHECK_THROWS_WITH(json(jobject.cbegin(), jobject2.cend()), "iterators are not compatible"); + CHECK_THROWS_WITH(json(jobject2.cbegin(), jobject.cend()), "iterators are not compatible"); } } } @@ -1029,12 +1035,16 @@ TEST_CASE("constructors") json jarray2 = {2, 3, 4, 5}; CHECK_THROWS_AS(json(jarray.begin(), jarray2.end()), std::domain_error); CHECK_THROWS_AS(json(jarray2.begin(), jarray.end()), std::domain_error); + CHECK_THROWS_WITH(json(jarray.begin(), jarray2.end()), "iterators are not compatible"); + CHECK_THROWS_WITH(json(jarray2.begin(), jarray.end()), "iterators are not compatible"); } { json jarray = {1, 2, 3, 4}; json jarray2 = {2, 3, 4, 5}; CHECK_THROWS_AS(json(jarray.cbegin(), jarray2.cend()), std::domain_error); CHECK_THROWS_AS(json(jarray2.cbegin(), jarray.cend()), std::domain_error); + CHECK_THROWS_WITH(json(jarray.cbegin(), jarray2.cend()), "iterators are not compatible"); + CHECK_THROWS_WITH(json(jarray2.cbegin(), jarray.cend()), "iterators are not compatible"); } } } @@ -1048,10 +1058,12 @@ TEST_CASE("constructors") { json j; CHECK_THROWS_AS(json(j.begin(), j.end()), std::domain_error); + CHECK_THROWS_WITH(json(j.begin(), j.end()), "cannot use construct with iterators from null"); } { json j; CHECK_THROWS_AS(json(j.cbegin(), j.cend()), std::domain_error); + CHECK_THROWS_WITH(json(j.cbegin(), j.cend()), "cannot use construct with iterators from null"); } } @@ -1120,11 +1132,15 @@ TEST_CASE("constructors") json j = "foo"; CHECK_THROWS_AS(json(j.end(), j.end()), std::out_of_range); CHECK_THROWS_AS(json(j.begin(), j.begin()), std::out_of_range); + CHECK_THROWS_WITH(json(j.end(), j.end()), "iterators out of range"); + CHECK_THROWS_WITH(json(j.begin(), j.begin()), "iterators out of range"); } { json j = "bar"; CHECK_THROWS_AS(json(j.cend(), j.cend()), std::out_of_range); CHECK_THROWS_AS(json(j.cbegin(), j.cbegin()), std::out_of_range); + CHECK_THROWS_WITH(json(j.cend(), j.cend()), "iterators out of range"); + CHECK_THROWS_WITH(json(j.cbegin(), j.cbegin()), "iterators out of range"); } } @@ -1134,11 +1150,15 @@ TEST_CASE("constructors") json j = false; CHECK_THROWS_AS(json(j.end(), j.end()), std::out_of_range); CHECK_THROWS_AS(json(j.begin(), j.begin()), std::out_of_range); + CHECK_THROWS_WITH(json(j.end(), j.end()), "iterators out of range"); + CHECK_THROWS_WITH(json(j.begin(), j.begin()), "iterators out of range"); } { json j = true; CHECK_THROWS_AS(json(j.cend(), j.cend()), std::out_of_range); CHECK_THROWS_AS(json(j.cbegin(), j.cbegin()), std::out_of_range); + CHECK_THROWS_WITH(json(j.cend(), j.cend()), "iterators out of range"); + CHECK_THROWS_WITH(json(j.cbegin(), j.cbegin()), "iterators out of range"); } } @@ -1148,11 +1168,15 @@ TEST_CASE("constructors") json j = 17; CHECK_THROWS_AS(json(j.end(), j.end()), std::out_of_range); CHECK_THROWS_AS(json(j.begin(), j.begin()), std::out_of_range); + CHECK_THROWS_WITH(json(j.end(), j.end()), "iterators out of range"); + CHECK_THROWS_WITH(json(j.begin(), j.begin()), "iterators out of range"); } { json j = 17; CHECK_THROWS_AS(json(j.cend(), j.cend()), std::out_of_range); CHECK_THROWS_AS(json(j.cbegin(), j.cbegin()), std::out_of_range); + CHECK_THROWS_WITH(json(j.cend(), j.cend()), "iterators out of range"); + CHECK_THROWS_WITH(json(j.cbegin(), j.cbegin()), "iterators out of range"); } } @@ -1162,11 +1186,15 @@ TEST_CASE("constructors") json j = 23.42; CHECK_THROWS_AS(json(j.end(), j.end()), std::out_of_range); CHECK_THROWS_AS(json(j.begin(), j.begin()), std::out_of_range); + CHECK_THROWS_WITH(json(j.end(), j.end()), "iterators out of range"); + CHECK_THROWS_WITH(json(j.begin(), j.begin()), "iterators out of range"); } { json j = 23.42; CHECK_THROWS_AS(json(j.cend(), j.cend()), std::out_of_range); CHECK_THROWS_AS(json(j.cbegin(), j.cbegin()), std::out_of_range); + CHECK_THROWS_WITH(json(j.cend(), j.cend()), "iterators out of range"); + CHECK_THROWS_WITH(json(j.cbegin(), j.cbegin()), "iterators out of range"); } } } @@ -1645,6 +1673,19 @@ TEST_CASE("value conversion") CHECK_THROWS_AS(json(json::value_t::boolean).get(), std::logic_error); CHECK_THROWS_AS(json(json::value_t::number_integer).get(), std::logic_error); CHECK_THROWS_AS(json(json::value_t::number_float).get(), std::logic_error); + + CHECK_THROWS_WITH(json(json::value_t::null).get(), + "type must be object, but is null"); + CHECK_THROWS_WITH(json(json::value_t::array).get(), + "type must be object, but is array"); + CHECK_THROWS_WITH(json(json::value_t::string).get(), + "type must be object, but is string"); + CHECK_THROWS_WITH(json(json::value_t::boolean).get(), + "type must be object, but is boolean"); + CHECK_THROWS_WITH(json(json::value_t::number_integer).get(), + "type must be object, but is number"); + CHECK_THROWS_WITH(json(json::value_t::number_float).get(), + "type must be object, but is number"); } } @@ -1727,6 +1768,19 @@ TEST_CASE("value conversion") CHECK_THROWS_AS(json(json::value_t::boolean).get(), std::logic_error); CHECK_THROWS_AS(json(json::value_t::number_integer).get(), std::logic_error); CHECK_THROWS_AS(json(json::value_t::number_float).get(), std::logic_error); + + CHECK_THROWS_WITH(json(json::value_t::null).get(), + "type must be array, but is null"); + CHECK_THROWS_WITH(json(json::value_t::object).get(), + "type must be array, but is object"); + CHECK_THROWS_WITH(json(json::value_t::string).get(), + "type must be array, but is string"); + CHECK_THROWS_WITH(json(json::value_t::boolean).get(), + "type must be array, but is boolean"); + CHECK_THROWS_WITH(json(json::value_t::number_integer).get(), + "type must be array, but is number"); + CHECK_THROWS_WITH(json(json::value_t::number_float).get(), + "type must be array, but is number"); } } @@ -1791,6 +1845,19 @@ TEST_CASE("value conversion") CHECK_THROWS_AS(json(json::value_t::boolean).get(), std::logic_error); CHECK_THROWS_AS(json(json::value_t::number_integer).get(), std::logic_error); CHECK_THROWS_AS(json(json::value_t::number_float).get(), std::logic_error); + + CHECK_THROWS_WITH(json(json::value_t::null).get(), + "type must be string, but is null"); + CHECK_THROWS_WITH(json(json::value_t::object).get(), + "type must be string, but is object"); + CHECK_THROWS_WITH(json(json::value_t::array).get(), + "type must be string, but is array"); + CHECK_THROWS_WITH(json(json::value_t::boolean).get(), + "type must be string, but is boolean"); + CHECK_THROWS_WITH(json(json::value_t::number_integer).get(), + "type must be string, but is number"); + CHECK_THROWS_WITH(json(json::value_t::number_float).get(), + "type must be string, but is number"); } } @@ -1837,6 +1904,19 @@ TEST_CASE("value conversion") CHECK_THROWS_AS(json(json::value_t::string).get(), std::logic_error); CHECK_THROWS_AS(json(json::value_t::number_integer).get(), std::logic_error); CHECK_THROWS_AS(json(json::value_t::number_float).get(), std::logic_error); + + CHECK_THROWS_WITH(json(json::value_t::null).get(), + "type must be boolean, but is null"); + CHECK_THROWS_WITH(json(json::value_t::object).get(), + "type must be boolean, but is object"); + CHECK_THROWS_WITH(json(json::value_t::array).get(), + "type must be boolean, but is array"); + CHECK_THROWS_WITH(json(json::value_t::string).get(), + "type must be boolean, but is string"); + CHECK_THROWS_WITH(json(json::value_t::number_integer).get(), + "type must be boolean, but is number"); + CHECK_THROWS_WITH(json(json::value_t::number_float).get(), + "type must be boolean, but is number"); } } @@ -2068,6 +2148,18 @@ TEST_CASE("value conversion") CHECK_THROWS_AS(json(json::value_t::array).get(), std::logic_error); CHECK_THROWS_AS(json(json::value_t::string).get(), std::logic_error); CHECK_THROWS_AS(json(json::value_t::boolean).get(), std::logic_error); + + CHECK_THROWS_WITH(json(json::value_t::null).get(), + "type must be number, but is null"); + CHECK_THROWS_WITH(json(json::value_t::object).get(), + "type must be number, but is object"); + CHECK_THROWS_WITH(json(json::value_t::array).get(), + "type must be number, but is array"); + CHECK_THROWS_WITH(json(json::value_t::string).get(), + "type must be number, but is string"); + CHECK_THROWS_WITH(json(json::value_t::boolean).get(), + "type must be number, but is boolean"); + CHECK_NOTHROW(json(json::value_t::number_float).get()); } } @@ -2306,6 +2398,18 @@ TEST_CASE("value conversion") CHECK_THROWS_AS(json(json::value_t::array).get(), std::logic_error); CHECK_THROWS_AS(json(json::value_t::string).get(), std::logic_error); CHECK_THROWS_AS(json(json::value_t::boolean).get(), std::logic_error); + + CHECK_THROWS_WITH(json(json::value_t::null).get(), + "type must be number, but is null"); + CHECK_THROWS_WITH(json(json::value_t::object).get(), + "type must be number, but is object"); + CHECK_THROWS_WITH(json(json::value_t::array).get(), + "type must be number, but is array"); + CHECK_THROWS_WITH(json(json::value_t::string).get(), + "type must be number, but is string"); + CHECK_THROWS_WITH(json(json::value_t::boolean).get(), + "type must be number, but is boolean"); + CHECK_NOTHROW(json(json::value_t::number_integer).get()); } } @@ -2381,6 +2485,7 @@ TEST_CASE("value conversion") SECTION("exception in case of a non-object type") { CHECK_THROWS_AS((json().get>()), std::logic_error); + CHECK_THROWS_WITH((json().get>()), "type must be object, but is null"); } } @@ -2445,6 +2550,11 @@ TEST_CASE("value conversion") CHECK_THROWS_AS((json().get>()), std::logic_error); CHECK_THROWS_AS((json().get>()), std::logic_error); CHECK_THROWS_AS((json().get>()), std::logic_error); + + CHECK_THROWS_WITH((json().get>()), "type must be array, but is null"); + CHECK_THROWS_WITH((json().get>()), "type must be array, but is null"); + CHECK_THROWS_WITH((json().get>()), "type must be array, but is null"); + CHECK_THROWS_WITH((json().get>()), "type must be array, but is null"); } } } @@ -2848,6 +2958,9 @@ TEST_CASE("element access") { CHECK_THROWS_AS(j.at(7), std::out_of_range); CHECK_THROWS_AS(j_const.at(7), std::out_of_range); + + CHECK_THROWS_WITH(j.at(7), "array index 7 is out of range"); + CHECK_THROWS_WITH(j_const.at(7), "array index 7 is out of range"); } SECTION("access on non-array type") @@ -2858,6 +2971,9 @@ TEST_CASE("element access") const json j_nonarray_const(j_nonarray); CHECK_THROWS_AS(j_nonarray.at(0), std::domain_error); CHECK_THROWS_AS(j_nonarray_const.at(0), std::domain_error); + + CHECK_THROWS_WITH(j_nonarray.at(0), "cannot use at() with null"); + CHECK_THROWS_WITH(j_nonarray_const.at(0), "cannot use at() with null"); } SECTION("boolean") @@ -2866,6 +2982,9 @@ TEST_CASE("element access") const json j_nonarray_const(j_nonarray); CHECK_THROWS_AS(j_nonarray.at(0), std::domain_error); CHECK_THROWS_AS(j_nonarray_const.at(0), std::domain_error); + + CHECK_THROWS_WITH(j_nonarray.at(0), "cannot use at() with boolean"); + CHECK_THROWS_WITH(j_nonarray_const.at(0), "cannot use at() with boolean"); } SECTION("string") @@ -2874,6 +2993,9 @@ TEST_CASE("element access") const json j_nonarray_const(j_nonarray); CHECK_THROWS_AS(j_nonarray.at(0), std::domain_error); CHECK_THROWS_AS(j_nonarray_const.at(0), std::domain_error); + + CHECK_THROWS_WITH(j_nonarray.at(0), "cannot use at() with string"); + CHECK_THROWS_WITH(j_nonarray_const.at(0), "cannot use at() with string"); } SECTION("object") @@ -2882,6 +3004,9 @@ TEST_CASE("element access") const json j_nonarray_const(j_nonarray); CHECK_THROWS_AS(j_nonarray.at(0), std::domain_error); CHECK_THROWS_AS(j_nonarray_const.at(0), std::domain_error); + + CHECK_THROWS_WITH(j_nonarray.at(0), "cannot use at() with object"); + CHECK_THROWS_WITH(j_nonarray_const.at(0), "cannot use at() with object"); } SECTION("number (integer)") @@ -2890,6 +3015,9 @@ TEST_CASE("element access") const json j_nonarray_const(j_nonarray); CHECK_THROWS_AS(j_nonarray.at(0), std::domain_error); CHECK_THROWS_AS(j_nonarray_const.at(0), std::domain_error); + + CHECK_THROWS_WITH(j_nonarray.at(0), "cannot use at() with number"); + CHECK_THROWS_WITH(j_nonarray_const.at(0), "cannot use at() with number"); } SECTION("number (floating-point)") @@ -2898,6 +3026,9 @@ TEST_CASE("element access") const json j_nonarray_const(j_nonarray); CHECK_THROWS_AS(j_nonarray.at(0), std::domain_error); CHECK_THROWS_AS(j_nonarray_const.at(0), std::domain_error); + + CHECK_THROWS_WITH(j_nonarray.at(0), "cannot use at() with number"); + CHECK_THROWS_WITH(j_nonarray_const.at(0), "cannot use at() with number"); } } } @@ -2941,6 +3072,7 @@ TEST_CASE("element access") const json j_nonarray_const(j_nonarray); CHECK_NOTHROW(j_nonarray[0]); CHECK_THROWS_AS(j_nonarray_const[0], std::domain_error); + CHECK_THROWS_WITH(j_nonarray_const[0], "cannot use operator[] with null"); } SECTION("implicit transformation to properly filled array") @@ -2957,6 +3089,8 @@ TEST_CASE("element access") const json j_nonarray_const(j_nonarray); CHECK_THROWS_AS(j_nonarray[0], std::domain_error); CHECK_THROWS_AS(j_nonarray_const[0], std::domain_error); + CHECK_THROWS_WITH(j_nonarray[0], "cannot use operator[] with boolean"); + CHECK_THROWS_WITH(j_nonarray_const[0], "cannot use operator[] with boolean"); } SECTION("string") @@ -2965,6 +3099,8 @@ TEST_CASE("element access") const json j_nonarray_const(j_nonarray); CHECK_THROWS_AS(j_nonarray[0], std::domain_error); CHECK_THROWS_AS(j_nonarray_const[0], std::domain_error); + CHECK_THROWS_WITH(j_nonarray[0], "cannot use operator[] with string"); + CHECK_THROWS_WITH(j_nonarray_const[0], "cannot use operator[] with string"); } SECTION("object") @@ -2973,6 +3109,8 @@ TEST_CASE("element access") const json j_nonarray_const(j_nonarray); CHECK_THROWS_AS(j_nonarray[0], std::domain_error); CHECK_THROWS_AS(j_nonarray_const[0], std::domain_error); + CHECK_THROWS_WITH(j_nonarray[0], "cannot use operator[] with object"); + CHECK_THROWS_WITH(j_nonarray_const[0], "cannot use operator[] with object"); } SECTION("number (integer)") @@ -2981,6 +3119,8 @@ TEST_CASE("element access") const json j_nonarray_const(j_nonarray); CHECK_THROWS_AS(j_nonarray[0], std::domain_error); CHECK_THROWS_AS(j_nonarray_const[0], std::domain_error); + CHECK_THROWS_WITH(j_nonarray[0], "cannot use operator[] with number"); + CHECK_THROWS_WITH(j_nonarray_const[0], "cannot use operator[] with number"); } SECTION("number (floating-point)") @@ -2989,6 +3129,8 @@ TEST_CASE("element access") const json j_nonarray_const(j_nonarray); CHECK_THROWS_AS(j_nonarray[0], std::domain_error); CHECK_THROWS_AS(j_nonarray_const[0], std::domain_error); + CHECK_THROWS_WITH(j_nonarray[0], "cannot use operator[] with number"); + CHECK_THROWS_WITH(j_nonarray_const[0], "cannot use operator[] with number"); } } } @@ -3035,6 +3177,7 @@ TEST_CASE("element access") { json jarray = {1, true, nullptr, "string", 42.23, json::object(), {1, 2, 3}}; CHECK_THROWS_AS(jarray.erase(7), std::out_of_range); + CHECK_THROWS_WITH(jarray.erase(7), "index out of range"); } } @@ -3131,6 +3274,14 @@ TEST_CASE("element access") CHECK_THROWS_AS(jarray.erase(jarray.begin(), jarray2.end()), std::domain_error); CHECK_THROWS_AS(jarray.erase(jarray2.begin(), jarray.end()), std::domain_error); CHECK_THROWS_AS(jarray.erase(jarray2.begin(), jarray2.end()), std::domain_error); + + CHECK_THROWS_WITH(jarray.erase(jarray2.begin()), "iterator does not fit current value"); + CHECK_THROWS_WITH(jarray.erase(jarray.begin(), jarray2.end()), + "iterators do not fit current value"); + CHECK_THROWS_WITH(jarray.erase(jarray2.begin(), jarray.end()), + "iterators do not fit current value"); + CHECK_THROWS_WITH(jarray.erase(jarray2.begin(), jarray2.end()), + "iterators do not fit current value"); } { json jarray = {1, true, nullptr, "string", 42.23, json::object(), {1, 2, 3}}; @@ -3139,6 +3290,14 @@ TEST_CASE("element access") CHECK_THROWS_AS(jarray.erase(jarray.cbegin(), jarray2.cend()), std::domain_error); CHECK_THROWS_AS(jarray.erase(jarray2.cbegin(), jarray.cend()), std::domain_error); CHECK_THROWS_AS(jarray.erase(jarray2.cbegin(), jarray2.cend()), std::domain_error); + + CHECK_THROWS_WITH(jarray.erase(jarray2.cbegin()), "iterator does not fit current value"); + CHECK_THROWS_WITH(jarray.erase(jarray.cbegin(), jarray2.cend()), + "iterators do not fit current value"); + CHECK_THROWS_WITH(jarray.erase(jarray2.cbegin(), jarray.cend()), + "iterators do not fit current value"); + CHECK_THROWS_WITH(jarray.erase(jarray2.cbegin(), jarray2.cend()), + "iterators do not fit current value"); } } } @@ -3149,36 +3308,42 @@ TEST_CASE("element access") { json j_nonobject(json::value_t::null); CHECK_THROWS_AS(j_nonobject.erase(0), std::domain_error); + CHECK_THROWS_WITH(j_nonobject.erase(0), "cannot use erase() with null"); } SECTION("boolean") { json j_nonobject(json::value_t::boolean); CHECK_THROWS_AS(j_nonobject.erase(0), std::domain_error); + CHECK_THROWS_WITH(j_nonobject.erase(0), "cannot use erase() with boolean"); } SECTION("string") { json j_nonobject(json::value_t::string); CHECK_THROWS_AS(j_nonobject.erase(0), std::domain_error); + CHECK_THROWS_WITH(j_nonobject.erase(0), "cannot use erase() with string"); } SECTION("object") { json j_nonobject(json::value_t::object); CHECK_THROWS_AS(j_nonobject.erase(0), std::domain_error); + CHECK_THROWS_WITH(j_nonobject.erase(0), "cannot use erase() with object"); } SECTION("number (integer)") { json j_nonobject(json::value_t::number_integer); CHECK_THROWS_AS(j_nonobject.erase(0), std::domain_error); + CHECK_THROWS_WITH(j_nonobject.erase(0), "cannot use erase() with number"); } SECTION("number (floating-point)") { json j_nonobject(json::value_t::number_float); CHECK_THROWS_AS(j_nonobject.erase(0), std::domain_error); + CHECK_THROWS_WITH(j_nonobject.erase(0), "cannot use erase() with number"); } } } @@ -3214,6 +3379,8 @@ TEST_CASE("element access") { CHECK_THROWS_AS(j.at("foo"), std::out_of_range); CHECK_THROWS_AS(j_const.at("foo"), std::out_of_range); + CHECK_THROWS_WITH(j.at("foo"), "key 'foo' not found"); + CHECK_THROWS_WITH(j_const.at("foo"), "key 'foo' not found"); } SECTION("access on non-object type") @@ -3224,6 +3391,8 @@ TEST_CASE("element access") const json j_nonobject_const(j_nonobject); CHECK_THROWS_AS(j_nonobject.at("foo"), std::domain_error); CHECK_THROWS_AS(j_nonobject_const.at("foo"), std::domain_error); + CHECK_THROWS_WITH(j_nonobject.at("foo"), "cannot use at() with null"); + CHECK_THROWS_WITH(j_nonobject_const.at("foo"), "cannot use at() with null"); } SECTION("boolean") @@ -3232,6 +3401,8 @@ TEST_CASE("element access") const json j_nonobject_const(j_nonobject); CHECK_THROWS_AS(j_nonobject.at("foo"), std::domain_error); CHECK_THROWS_AS(j_nonobject_const.at("foo"), std::domain_error); + CHECK_THROWS_WITH(j_nonobject.at("foo"), "cannot use at() with boolean"); + CHECK_THROWS_WITH(j_nonobject_const.at("foo"), "cannot use at() with boolean"); } SECTION("string") @@ -3240,6 +3411,8 @@ TEST_CASE("element access") const json j_nonobject_const(j_nonobject); CHECK_THROWS_AS(j_nonobject.at("foo"), std::domain_error); CHECK_THROWS_AS(j_nonobject_const.at("foo"), std::domain_error); + CHECK_THROWS_WITH(j_nonobject.at("foo"), "cannot use at() with string"); + CHECK_THROWS_WITH(j_nonobject_const.at("foo"), "cannot use at() with string"); } SECTION("array") @@ -3248,6 +3421,8 @@ TEST_CASE("element access") const json j_nonobject_const(j_nonobject); CHECK_THROWS_AS(j_nonobject.at("foo"), std::domain_error); CHECK_THROWS_AS(j_nonobject_const.at("foo"), std::domain_error); + CHECK_THROWS_WITH(j_nonobject.at("foo"), "cannot use at() with array"); + CHECK_THROWS_WITH(j_nonobject_const.at("foo"), "cannot use at() with array"); } SECTION("number (integer)") @@ -3256,6 +3431,8 @@ TEST_CASE("element access") const json j_nonobject_const(j_nonobject); CHECK_THROWS_AS(j_nonobject.at("foo"), std::domain_error); CHECK_THROWS_AS(j_nonobject_const.at("foo"), std::domain_error); + CHECK_THROWS_WITH(j_nonobject.at("foo"), "cannot use at() with number"); + CHECK_THROWS_WITH(j_nonobject_const.at("foo"), "cannot use at() with number"); } SECTION("number (floating-point)") @@ -3264,6 +3441,115 @@ TEST_CASE("element access") const json j_nonobject_const(j_nonobject); CHECK_THROWS_AS(j_nonobject.at("foo"), std::domain_error); CHECK_THROWS_AS(j_nonobject_const.at("foo"), std::domain_error); + CHECK_THROWS_WITH(j_nonobject.at("foo"), "cannot use at() with number"); + CHECK_THROWS_WITH(j_nonobject_const.at("foo"), "cannot use at() with number"); + } + } + } + + SECTION("access specified element with default value") + { + SECTION("access existing value") + { + CHECK(j.value("integer", 2) == 1); + CHECK(j.value("integer", 1.0) == Approx(1)); + CHECK(j.value("null", json(1)) == json()); + CHECK(j.value("boolean", false) == true); + CHECK(j.value("string", "bar") == "hello world"); + CHECK(j.value("string", std::string("bar")) == "hello world"); + CHECK(j.value("floating", 12.34) == Approx(42.23)); + CHECK(j.value("floating", 12) == 42); + CHECK(j.value("object", json({{"foo", "bar"}})) == json(json::object())); + CHECK(j.value("array", json({10, 100})) == json({1, 2, 3})); + + CHECK(j_const.value("integer", 2) == 1); + CHECK(j_const.value("integer", 1.0) == Approx(1)); + CHECK(j_const.value("boolean", false) == true); + CHECK(j_const.value("string", "bar") == "hello world"); + CHECK(j_const.value("string", std::string("bar")) == "hello world"); + CHECK(j_const.value("floating", 12.34) == Approx(42.23)); + CHECK(j_const.value("floating", 12) == 42); + CHECK(j_const.value("object", json({{"foo", "bar"}})) == json(json::object())); + CHECK(j_const.value("array", json({10, 100})) == json({1, 2, 3})); + } + + SECTION("access non-existing value") + { + CHECK(j.value("_", 2) == 2); + CHECK(j.value("_", false) == false); + CHECK(j.value("_", "bar") == "bar"); + CHECK(j.value("_", 12.34) == Approx(12.34)); + CHECK(j.value("_", json({{"foo", "bar"}})) == json({{"foo", "bar"}})); + CHECK(j.value("_", json({10, 100})) == json({10, 100})); + + CHECK(j_const.value("_", 2) == 2); + CHECK(j_const.value("_", false) == false); + CHECK(j_const.value("_", "bar") == "bar"); + CHECK(j_const.value("_", 12.34) == Approx(12.34)); + CHECK(j_const.value("_", json({{"foo", "bar"}})) == json({{"foo", "bar"}})); + CHECK(j_const.value("_", json({10, 100})) == json({10, 100})); + } + + SECTION("access on non-object type") + { + SECTION("null") + { + json j_nonobject(json::value_t::null); + const json j_nonobject_const(j_nonobject); + CHECK_THROWS_AS(j_nonobject.value("foo", 1), std::domain_error); + CHECK_THROWS_AS(j_nonobject_const.value("foo", 1), std::domain_error); + CHECK_THROWS_WITH(j_nonobject.value("foo", 1), "cannot use value() with null"); + CHECK_THROWS_WITH(j_nonobject_const.value("foo", 1), "cannot use value() with null"); + } + + SECTION("boolean") + { + json j_nonobject(json::value_t::boolean); + const json j_nonobject_const(j_nonobject); + CHECK_THROWS_AS(j_nonobject.value("foo", 1), std::domain_error); + CHECK_THROWS_AS(j_nonobject_const.value("foo", 1), std::domain_error); + CHECK_THROWS_WITH(j_nonobject.value("foo", 1), "cannot use value() with boolean"); + CHECK_THROWS_WITH(j_nonobject_const.value("foo", 1), "cannot use value() with boolean"); + } + + SECTION("string") + { + json j_nonobject(json::value_t::string); + const json j_nonobject_const(j_nonobject); + CHECK_THROWS_AS(j_nonobject.value("foo", 1), std::domain_error); + CHECK_THROWS_AS(j_nonobject_const.value("foo", 1), std::domain_error); + CHECK_THROWS_WITH(j_nonobject.value("foo", 1), "cannot use value() with string"); + CHECK_THROWS_WITH(j_nonobject_const.value("foo", 1), "cannot use value() with string"); + } + + SECTION("array") + { + json j_nonobject(json::value_t::array); + const json j_nonobject_const(j_nonobject); + CHECK_THROWS_AS(j_nonobject.value("foo", 1), std::domain_error); + CHECK_THROWS_AS(j_nonobject_const.value("foo", 1), std::domain_error); + CHECK_THROWS_WITH(j_nonobject.value("foo", 1), "cannot use value() with array"); + CHECK_THROWS_WITH(j_nonobject_const.value("foo", 1), "cannot use value() with array"); + } + + SECTION("number (integer)") + { + json j_nonobject(json::value_t::number_integer); + const json j_nonobject_const(j_nonobject); + CHECK_THROWS_AS(j_nonobject.value("foo", 1), std::domain_error); + CHECK_THROWS_AS(j_nonobject_const.value("foo", 1), std::domain_error); + CHECK_THROWS_WITH(j_nonobject.value("foo", 1), "cannot use value() with number"); + CHECK_THROWS_WITH(j_nonobject_const.value("foo", 1), "cannot use value() with number"); + } + + SECTION("number (floating-point)") + { + json j_nonobject(json::value_t::number_float); + const json j_nonobject_const(j_nonobject); + CHECK_THROWS_AS(j_nonobject.value("foo", 1), std::domain_error); + CHECK_THROWS_AS(j_nonobject_const.value("foo", 1), std::domain_error); + CHECK_THROWS_WITH(j_nonobject.value("foo", 1), "cannot use value() with number"); + CHECK_THROWS_WITH(j_nonobject_const.value("foo", 1), "cannot use value() with number"); } } } @@ -3284,36 +3570,43 @@ TEST_CASE("element access") { CHECK(j["integer"] == json(1)); CHECK(j[json::object_t::key_type("integer")] == j["integer"]); - CHECK(j_const["integer"] == json(1)); - CHECK(j_const[json::object_t::key_type("integer")] == j["integer"]); CHECK(j["boolean"] == json(true)); CHECK(j[json::object_t::key_type("boolean")] == j["boolean"]); - CHECK(j_const["boolean"] == json(true)); - CHECK(j_const[json::object_t::key_type("boolean")] == j["boolean"]); CHECK(j["null"] == json(nullptr)); CHECK(j[json::object_t::key_type("null")] == j["null"]); - CHECK(j_const["null"] == json(nullptr)); - CHECK(j_const[json::object_t::key_type("null")] == j["null"]); CHECK(j["string"] == json("hello world")); CHECK(j[json::object_t::key_type("string")] == j["string"]); - CHECK(j_const["string"] == json("hello world")); - CHECK(j_const[json::object_t::key_type("string")] == j["string"]); CHECK(j["floating"] == json(42.23)); CHECK(j[json::object_t::key_type("floating")] == j["floating"]); - CHECK(j_const["floating"] == json(42.23)); - CHECK(j_const[json::object_t::key_type("floating")] == j["floating"]); CHECK(j["object"] == json(json::object())); CHECK(j[json::object_t::key_type("object")] == j["object"]); - CHECK(j_const["object"] == json(json::object())); - CHECK(j_const[json::object_t::key_type("object")] == j["object"]); CHECK(j["array"] == json({1, 2, 3})); CHECK(j[json::object_t::key_type("array")] == j["array"]); + + CHECK(j_const["integer"] == json(1)); + CHECK(j_const[json::object_t::key_type("integer")] == j["integer"]); + + CHECK(j_const["boolean"] == json(true)); + CHECK(j_const[json::object_t::key_type("boolean")] == j["boolean"]); + + CHECK(j_const["null"] == json(nullptr)); + CHECK(j_const[json::object_t::key_type("null")] == j["null"]); + + CHECK(j_const["string"] == json("hello world")); + CHECK(j_const[json::object_t::key_type("string")] == j["string"]); + + CHECK(j_const["floating"] == json(42.23)); + CHECK(j_const[json::object_t::key_type("floating")] == j["floating"]); + + CHECK(j_const["object"] == json(json::object())); + CHECK(j_const[json::object_t::key_type("object")] == j["object"]); + CHECK(j_const["array"] == json({1, 2, 3})); CHECK(j_const[json::object_t::key_type("array")] == j["array"]); } @@ -3329,6 +3622,9 @@ TEST_CASE("element access") CHECK_NOTHROW(j_nonobject2[json::object_t::key_type("foo")]); CHECK_THROWS_AS(j_const_nonobject["foo"], std::domain_error); CHECK_THROWS_AS(j_const_nonobject[json::object_t::key_type("foo")], std::domain_error); + CHECK_THROWS_WITH(j_const_nonobject["foo"], "cannot use operator[] with null"); + CHECK_THROWS_WITH(j_const_nonobject[json::object_t::key_type("foo")], + "cannot use operator[] with null"); } SECTION("boolean") @@ -3339,6 +3635,12 @@ TEST_CASE("element access") CHECK_THROWS_AS(j_nonobject[json::object_t::key_type("foo")], std::domain_error); CHECK_THROWS_AS(j_const_nonobject["foo"], std::domain_error); CHECK_THROWS_AS(j_const_nonobject[json::object_t::key_type("foo")], std::domain_error); + CHECK_THROWS_WITH(j_nonobject["foo"], "cannot use operator[] with boolean"); + CHECK_THROWS_WITH(j_nonobject[json::object_t::key_type("foo")], + "cannot use operator[] with boolean"); + CHECK_THROWS_WITH(j_const_nonobject["foo"], "cannot use operator[] with boolean"); + CHECK_THROWS_WITH(j_const_nonobject[json::object_t::key_type("foo")], + "cannot use operator[] with boolean"); } SECTION("string") @@ -3349,6 +3651,12 @@ TEST_CASE("element access") CHECK_THROWS_AS(j_nonobject[json::object_t::key_type("foo")], std::domain_error); CHECK_THROWS_AS(j_const_nonobject["foo"], std::domain_error); CHECK_THROWS_AS(j_const_nonobject[json::object_t::key_type("foo")], std::domain_error); + CHECK_THROWS_WITH(j_nonobject["foo"], "cannot use operator[] with string"); + CHECK_THROWS_WITH(j_nonobject[json::object_t::key_type("foo")], + "cannot use operator[] with string"); + CHECK_THROWS_WITH(j_const_nonobject["foo"], "cannot use operator[] with string"); + CHECK_THROWS_WITH(j_const_nonobject[json::object_t::key_type("foo")], + "cannot use operator[] with string"); } SECTION("array") @@ -3359,6 +3667,11 @@ TEST_CASE("element access") CHECK_THROWS_AS(j_nonobject[json::object_t::key_type("foo")], std::domain_error); CHECK_THROWS_AS(j_const_nonobject["foo"], std::domain_error); CHECK_THROWS_AS(j_const_nonobject[json::object_t::key_type("foo")], std::domain_error); + CHECK_THROWS_WITH(j_nonobject["foo"], "cannot use operator[] with array"); + CHECK_THROWS_WITH(j_nonobject[json::object_t::key_type("foo")], "cannot use operator[] with array"); + CHECK_THROWS_WITH(j_const_nonobject["foo"], "cannot use operator[] with array"); + CHECK_THROWS_WITH(j_const_nonobject[json::object_t::key_type("foo")], + "cannot use operator[] with array"); } SECTION("number (integer)") @@ -3369,6 +3682,12 @@ TEST_CASE("element access") CHECK_THROWS_AS(j_nonobject[json::object_t::key_type("foo")], std::domain_error); CHECK_THROWS_AS(j_const_nonobject["foo"], std::domain_error); CHECK_THROWS_AS(j_const_nonobject[json::object_t::key_type("foo")], std::domain_error); + CHECK_THROWS_WITH(j_nonobject["foo"], "cannot use operator[] with number"); + CHECK_THROWS_WITH(j_nonobject[json::object_t::key_type("foo")], + "cannot use operator[] with number"); + CHECK_THROWS_WITH(j_const_nonobject["foo"], "cannot use operator[] with number"); + CHECK_THROWS_WITH(j_const_nonobject[json::object_t::key_type("foo")], + "cannot use operator[] with number"); } SECTION("number (floating-point)") @@ -3379,6 +3698,12 @@ TEST_CASE("element access") CHECK_THROWS_AS(j_nonobject[json::object_t::key_type("foo")], std::domain_error); CHECK_THROWS_AS(j_const_nonobject["foo"], std::domain_error); CHECK_THROWS_AS(j_const_nonobject[json::object_t::key_type("foo")], std::domain_error); + CHECK_THROWS_WITH(j_nonobject["foo"], "cannot use operator[] with number"); + CHECK_THROWS_WITH(j_nonobject[json::object_t::key_type("foo")], + "cannot use operator[] with number"); + CHECK_THROWS_WITH(j_const_nonobject["foo"], "cannot use operator[] with number"); + CHECK_THROWS_WITH(j_const_nonobject[json::object_t::key_type("foo")], + "cannot use operator[] with number"); } } } @@ -3516,6 +3841,13 @@ TEST_CASE("element access") CHECK_THROWS_AS(jobject.erase(jobject.begin(), jobject2.end()), std::domain_error); CHECK_THROWS_AS(jobject.erase(jobject2.begin(), jobject.end()), std::domain_error); CHECK_THROWS_AS(jobject.erase(jobject2.begin(), jobject2.end()), std::domain_error); + CHECK_THROWS_WITH(jobject.erase(jobject2.begin()), "iterator does not fit current value"); + CHECK_THROWS_WITH(jobject.erase(jobject.begin(), jobject2.end()), + "iterators do not fit current value"); + CHECK_THROWS_WITH(jobject.erase(jobject2.begin(), jobject.end()), + "iterators do not fit current value"); + CHECK_THROWS_WITH(jobject.erase(jobject2.begin(), jobject2.end()), + "iterators do not fit current value"); } { json jobject = {{"a", "a"}, {"b", 1}, {"c", 17}, {"d", false}, {"e", true}}; @@ -3524,6 +3856,13 @@ TEST_CASE("element access") CHECK_THROWS_AS(jobject.erase(jobject.cbegin(), jobject2.cend()), std::domain_error); CHECK_THROWS_AS(jobject.erase(jobject2.cbegin(), jobject.cend()), std::domain_error); CHECK_THROWS_AS(jobject.erase(jobject2.cbegin(), jobject2.cend()), std::domain_error); + CHECK_THROWS_WITH(jobject.erase(jobject2.cbegin()), "iterator does not fit current value"); + CHECK_THROWS_WITH(jobject.erase(jobject.cbegin(), jobject2.cend()), + "iterators do not fit current value"); + CHECK_THROWS_WITH(jobject.erase(jobject2.cbegin(), jobject.cend()), + "iterators do not fit current value"); + CHECK_THROWS_WITH(jobject.erase(jobject2.cbegin(), jobject2.cend()), + "iterators do not fit current value"); } } } @@ -3534,36 +3873,42 @@ TEST_CASE("element access") { json j_nonobject(json::value_t::null); CHECK_THROWS_AS(j_nonobject.erase("foo"), std::domain_error); + CHECK_THROWS_WITH(j_nonobject.erase("foo"), "cannot use erase() with null"); } SECTION("boolean") { json j_nonobject(json::value_t::boolean); CHECK_THROWS_AS(j_nonobject.erase("foo"), std::domain_error); + CHECK_THROWS_WITH(j_nonobject.erase("foo"), "cannot use erase() with boolean"); } SECTION("string") { json j_nonobject(json::value_t::string); CHECK_THROWS_AS(j_nonobject.erase("foo"), std::domain_error); + CHECK_THROWS_WITH(j_nonobject.erase("foo"), "cannot use erase() with string"); } SECTION("array") { json j_nonobject(json::value_t::array); CHECK_THROWS_AS(j_nonobject.erase("foo"), std::domain_error); + CHECK_THROWS_WITH(j_nonobject.erase("foo"), "cannot use erase() with array"); } SECTION("number (integer)") { json j_nonobject(json::value_t::number_integer); CHECK_THROWS_AS(j_nonobject.erase("foo"), std::domain_error); + CHECK_THROWS_WITH(j_nonobject.erase("foo"), "cannot use erase() with number"); } SECTION("number (floating-point)") { json j_nonobject(json::value_t::number_float); CHECK_THROWS_AS(j_nonobject.erase("foo"), std::domain_error); + CHECK_THROWS_WITH(j_nonobject.erase("foo"), "cannot use erase() with number"); } } } @@ -3739,11 +4084,15 @@ TEST_CASE("element access") json j; CHECK_THROWS_AS(j.front(), std::out_of_range); CHECK_THROWS_AS(j.back(), std::out_of_range); + CHECK_THROWS_WITH(j.front(), "cannot get value"); + CHECK_THROWS_WITH(j.back(), "cannot get value"); } { const json j{}; CHECK_THROWS_AS(j.front(), std::out_of_range); CHECK_THROWS_AS(j.back(), std::out_of_range); + CHECK_THROWS_WITH(j.front(), "cannot get value"); + CHECK_THROWS_WITH(j.back(), "cannot get value"); } } @@ -3811,10 +4160,12 @@ TEST_CASE("element access") { json j; CHECK_THROWS_AS(j.erase(j.begin()), std::domain_error); + CHECK_THROWS_WITH(j.erase(j.begin()), "cannot use erase() with null"); } { json j; CHECK_THROWS_AS(j.erase(j.cbegin()), std::domain_error); + CHECK_THROWS_WITH(j.erase(j.begin()), "cannot use erase() with null"); } } @@ -3890,10 +4241,12 @@ TEST_CASE("element access") { json j = "foo"; CHECK_THROWS_AS(j.erase(j.end()), std::out_of_range); + CHECK_THROWS_WITH(j.erase(j.end()), "iterator out of range"); } { json j = "bar"; CHECK_THROWS_AS(j.erase(j.cend()), std::out_of_range); + CHECK_THROWS_WITH(j.erase(j.cend()), "iterator out of range"); } } @@ -3902,10 +4255,12 @@ TEST_CASE("element access") { json j = false; CHECK_THROWS_AS(j.erase(j.end()), std::out_of_range); + CHECK_THROWS_WITH(j.erase(j.end()), "iterator out of range"); } { json j = true; CHECK_THROWS_AS(j.erase(j.cend()), std::out_of_range); + CHECK_THROWS_WITH(j.erase(j.cend()), "iterator out of range"); } } @@ -3914,10 +4269,12 @@ TEST_CASE("element access") { json j = 17; CHECK_THROWS_AS(j.erase(j.end()), std::out_of_range); + CHECK_THROWS_WITH(j.erase(j.end()), "iterator out of range"); } { json j = 17; CHECK_THROWS_AS(j.erase(j.cend()), std::out_of_range); + CHECK_THROWS_WITH(j.erase(j.cend()), "iterator out of range"); } } @@ -3926,10 +4283,12 @@ TEST_CASE("element access") { json j = 23.42; CHECK_THROWS_AS(j.erase(j.end()), std::out_of_range); + CHECK_THROWS_WITH(j.erase(j.end()), "iterator out of range"); } { json j = 23.42; CHECK_THROWS_AS(j.erase(j.cend()), std::out_of_range); + CHECK_THROWS_WITH(j.erase(j.cend()), "iterator out of range"); } } } @@ -3941,10 +4300,12 @@ TEST_CASE("element access") { json j; CHECK_THROWS_AS(j.erase(j.begin(), j.end()), std::domain_error); + CHECK_THROWS_WITH(j.erase(j.begin(), j.end()), "cannot use erase() with null"); } { json j; CHECK_THROWS_AS(j.erase(j.cbegin(), j.cend()), std::domain_error); + CHECK_THROWS_WITH(j.erase(j.cbegin(), j.cend()), "cannot use erase() with null"); } } @@ -4021,11 +4382,15 @@ TEST_CASE("element access") json j = "foo"; CHECK_THROWS_AS(j.erase(j.end(), j.end()), std::out_of_range); CHECK_THROWS_AS(j.erase(j.begin(), j.begin()), std::out_of_range); + CHECK_THROWS_WITH(j.erase(j.end(), j.end()), "iterators out of range"); + CHECK_THROWS_WITH(j.erase(j.begin(), j.begin()), "iterators out of range"); } { json j = "bar"; CHECK_THROWS_AS(j.erase(j.cend(), j.cend()), std::out_of_range); CHECK_THROWS_AS(j.erase(j.cbegin(), j.cbegin()), std::out_of_range); + CHECK_THROWS_WITH(j.erase(j.cend(), j.cend()), "iterators out of range"); + CHECK_THROWS_WITH(j.erase(j.cbegin(), j.cbegin()), "iterators out of range"); } } @@ -4035,11 +4400,15 @@ TEST_CASE("element access") json j = false; CHECK_THROWS_AS(j.erase(j.end(), j.end()), std::out_of_range); CHECK_THROWS_AS(j.erase(j.begin(), j.begin()), std::out_of_range); + CHECK_THROWS_WITH(j.erase(j.end(), j.end()), "iterators out of range"); + CHECK_THROWS_WITH(j.erase(j.begin(), j.begin()), "iterators out of range"); } { json j = true; CHECK_THROWS_AS(j.erase(j.cend(), j.cend()), std::out_of_range); CHECK_THROWS_AS(j.erase(j.cbegin(), j.cbegin()), std::out_of_range); + CHECK_THROWS_WITH(j.erase(j.cend(), j.cend()), "iterators out of range"); + CHECK_THROWS_WITH(j.erase(j.cbegin(), j.cbegin()), "iterators out of range"); } } @@ -4049,11 +4418,15 @@ TEST_CASE("element access") json j = 17; CHECK_THROWS_AS(j.erase(j.end(), j.end()), std::out_of_range); CHECK_THROWS_AS(j.erase(j.begin(), j.begin()), std::out_of_range); + CHECK_THROWS_WITH(j.erase(j.end(), j.end()), "iterators out of range"); + CHECK_THROWS_WITH(j.erase(j.begin(), j.begin()), "iterators out of range"); } { json j = 17; CHECK_THROWS_AS(j.erase(j.cend(), j.cend()), std::out_of_range); CHECK_THROWS_AS(j.erase(j.cbegin(), j.cbegin()), std::out_of_range); + CHECK_THROWS_WITH(j.erase(j.cend(), j.cend()), "iterators out of range"); + CHECK_THROWS_WITH(j.erase(j.cbegin(), j.cbegin()), "iterators out of range"); } } @@ -4063,11 +4436,15 @@ TEST_CASE("element access") json j = 23.42; CHECK_THROWS_AS(j.erase(j.end(), j.end()), std::out_of_range); CHECK_THROWS_AS(j.erase(j.begin(), j.begin()), std::out_of_range); + CHECK_THROWS_WITH(j.erase(j.end(), j.end()), "iterators out of range"); + CHECK_THROWS_WITH(j.erase(j.begin(), j.begin()), "iterators out of range"); } { json j = 23.42; CHECK_THROWS_AS(j.erase(j.cend(), j.cend()), std::out_of_range); CHECK_THROWS_AS(j.erase(j.cbegin(), j.cbegin()), std::out_of_range); + CHECK_THROWS_WITH(j.erase(j.cend(), j.cend()), "iterators out of range"); + CHECK_THROWS_WITH(j.erase(j.cbegin(), j.cbegin()), "iterators out of range"); } } } @@ -4272,8 +4649,10 @@ TEST_CASE("iterators") auto it = j.begin(); auto cit = j_const.cbegin(); CHECK_THROWS_AS(it.key(), std::domain_error); + CHECK_THROWS_WITH(it.key(), "cannot use key() for non-object iterators"); CHECK(it.value() == json(true)); CHECK_THROWS_AS(cit.key(), std::domain_error); + CHECK_THROWS_WITH(cit.key(), "cannot use key() for non-object iterators"); CHECK(cit.value() == json(true)); auto rit = j.rend(); @@ -4282,6 +4661,10 @@ TEST_CASE("iterators") CHECK_THROWS_AS(rit.value(), std::out_of_range); CHECK_THROWS_AS(crit.key(), std::domain_error); CHECK_THROWS_AS(crit.value(), std::out_of_range); + CHECK_THROWS_WITH(rit.key(), "cannot use key() for non-object iterators"); + CHECK_THROWS_WITH(rit.value(), "cannot get value"); + CHECK_THROWS_WITH(crit.key(), "cannot use key() for non-object iterators"); + CHECK_THROWS_WITH(crit.value(), "cannot get value"); } } @@ -4470,8 +4853,10 @@ TEST_CASE("iterators") auto it = j.begin(); auto cit = j_const.cbegin(); CHECK_THROWS_AS(it.key(), std::domain_error); + CHECK_THROWS_WITH(it.key(), "cannot use key() for non-object iterators"); CHECK(it.value() == json("hello world")); CHECK_THROWS_AS(cit.key(), std::domain_error); + CHECK_THROWS_WITH(cit.key(), "cannot use key() for non-object iterators"); CHECK(cit.value() == json("hello world")); auto rit = j.rend(); @@ -4480,6 +4865,10 @@ TEST_CASE("iterators") CHECK_THROWS_AS(rit.value(), std::out_of_range); CHECK_THROWS_AS(crit.key(), std::domain_error); CHECK_THROWS_AS(crit.value(), std::out_of_range); + CHECK_THROWS_WITH(rit.key(), "cannot use key() for non-object iterators"); + CHECK_THROWS_WITH(rit.value(), "cannot get value"); + CHECK_THROWS_WITH(crit.key(), "cannot use key() for non-object iterators"); + CHECK_THROWS_WITH(crit.value(), "cannot get value"); } } @@ -4661,8 +5050,10 @@ TEST_CASE("iterators") auto it = j.begin(); auto cit = j_const.cbegin(); CHECK_THROWS_AS(it.key(), std::domain_error); + CHECK_THROWS_WITH(it.key(), "cannot use key() for non-object iterators"); CHECK(it.value() == json(1)); CHECK_THROWS_AS(cit.key(), std::domain_error); + CHECK_THROWS_WITH(cit.key(), "cannot use key() for non-object iterators"); CHECK(cit.value() == json(1)); } } @@ -5036,8 +5427,10 @@ TEST_CASE("iterators") auto it = j.begin(); auto cit = j_const.cbegin(); CHECK_THROWS_AS(it.key(), std::domain_error); + CHECK_THROWS_WITH(it.key(), "cannot use key() for non-object iterators"); CHECK(it.value() == json(23)); CHECK_THROWS_AS(cit.key(), std::domain_error); + CHECK_THROWS_WITH(cit.key(), "cannot use key() for non-object iterators"); CHECK(cit.value() == json(23)); auto rit = j.rend(); @@ -5046,6 +5439,10 @@ TEST_CASE("iterators") CHECK_THROWS_AS(rit.value(), std::out_of_range); CHECK_THROWS_AS(crit.key(), std::domain_error); CHECK_THROWS_AS(crit.value(), std::out_of_range); + CHECK_THROWS_WITH(rit.key(), "cannot use key() for non-object iterators"); + CHECK_THROWS_WITH(rit.value(), "cannot get value"); + CHECK_THROWS_WITH(crit.key(), "cannot use key() for non-object iterators"); + CHECK_THROWS_WITH(crit.value(), "cannot get value"); } } @@ -5234,8 +5631,10 @@ TEST_CASE("iterators") auto it = j.begin(); auto cit = j_const.cbegin(); CHECK_THROWS_AS(it.key(), std::domain_error); + CHECK_THROWS_WITH(it.key(), "cannot use key() for non-object iterators"); CHECK(it.value() == json(23.42)); CHECK_THROWS_AS(cit.key(), std::domain_error); + CHECK_THROWS_WITH(cit.key(), "cannot use key() for non-object iterators"); CHECK(cit.value() == json(23.42)); auto rit = j.rend(); @@ -5244,6 +5643,10 @@ TEST_CASE("iterators") CHECK_THROWS_AS(rit.value(), std::out_of_range); CHECK_THROWS_AS(crit.key(), std::domain_error); CHECK_THROWS_AS(crit.value(), std::out_of_range); + CHECK_THROWS_WITH(rit.key(), "cannot use key() for non-object iterators"); + CHECK_THROWS_WITH(rit.value(), "cannot get value"); + CHECK_THROWS_WITH(crit.key(), "cannot use key() for non-object iterators"); + CHECK_THROWS_WITH(crit.value(), "cannot get value"); } } @@ -5305,6 +5708,10 @@ TEST_CASE("iterators") CHECK_THROWS_AS(it.value(), std::out_of_range); CHECK_THROWS_AS(cit.key(), std::domain_error); CHECK_THROWS_AS(cit.value(), std::out_of_range); + CHECK_THROWS_WITH(it.key(), "cannot use key() for non-object iterators"); + CHECK_THROWS_WITH(it.value(), "cannot get value"); + CHECK_THROWS_WITH(cit.key(), "cannot use key() for non-object iterators"); + CHECK_THROWS_WITH(cit.value(), "cannot get value"); auto rit = j.rend(); auto crit = j.crend(); @@ -5312,6 +5719,10 @@ TEST_CASE("iterators") CHECK_THROWS_AS(rit.value(), std::out_of_range); CHECK_THROWS_AS(crit.key(), std::domain_error); CHECK_THROWS_AS(crit.value(), std::out_of_range); + CHECK_THROWS_WITH(rit.key(), "cannot use key() for non-object iterators"); + CHECK_THROWS_WITH(rit.value(), "cannot get value"); + CHECK_THROWS_WITH(crit.key(), "cannot use key() for non-object iterators"); + CHECK_THROWS_WITH(crit.value(), "cannot get value"); } } } @@ -5372,6 +5783,14 @@ TEST_CASE("iterators") CHECK_THROWS_AS(it1_c < it2_c, std::domain_error); CHECK_THROWS_AS(it2_c < it3_c, std::domain_error); CHECK_THROWS_AS(it1_c < it3_c, std::domain_error); + CHECK_THROWS_WITH(it1 < it1, "cannot compare order of object iterators"); + CHECK_THROWS_WITH(it1 < it2, "cannot compare order of object iterators"); + CHECK_THROWS_WITH(it2 < it3, "cannot compare order of object iterators"); + CHECK_THROWS_WITH(it1 < it3, "cannot compare order of object iterators"); + CHECK_THROWS_WITH(it1_c < it1_c, "cannot compare order of object iterators"); + CHECK_THROWS_WITH(it1_c < it2_c, "cannot compare order of object iterators"); + CHECK_THROWS_WITH(it2_c < it3_c, "cannot compare order of object iterators"); + CHECK_THROWS_WITH(it1_c < it3_c, "cannot compare order of object iterators"); } else { @@ -5398,6 +5817,14 @@ TEST_CASE("iterators") CHECK_THROWS_AS(it1_c <= it2_c, std::domain_error); CHECK_THROWS_AS(it2_c <= it3_c, std::domain_error); CHECK_THROWS_AS(it1_c <= it3_c, std::domain_error); + CHECK_THROWS_WITH(it1 <= it1, "cannot compare order of object iterators"); + CHECK_THROWS_WITH(it1 <= it2, "cannot compare order of object iterators"); + CHECK_THROWS_WITH(it2 <= it3, "cannot compare order of object iterators"); + CHECK_THROWS_WITH(it1 <= it3, "cannot compare order of object iterators"); + CHECK_THROWS_WITH(it1_c <= it1_c, "cannot compare order of object iterators"); + CHECK_THROWS_WITH(it1_c <= it2_c, "cannot compare order of object iterators"); + CHECK_THROWS_WITH(it2_c <= it3_c, "cannot compare order of object iterators"); + CHECK_THROWS_WITH(it1_c <= it3_c, "cannot compare order of object iterators"); } else { @@ -5425,6 +5852,14 @@ TEST_CASE("iterators") CHECK_THROWS_AS(it1_c > it2_c, std::domain_error); CHECK_THROWS_AS(it2_c > it3_c, std::domain_error); CHECK_THROWS_AS(it1_c > it3_c, std::domain_error); + CHECK_THROWS_WITH(it1 > it1, "cannot compare order of object iterators"); + CHECK_THROWS_WITH(it1 > it2, "cannot compare order of object iterators"); + CHECK_THROWS_WITH(it2 > it3, "cannot compare order of object iterators"); + CHECK_THROWS_WITH(it1 > it3, "cannot compare order of object iterators"); + CHECK_THROWS_WITH(it1_c > it1_c, "cannot compare order of object iterators"); + CHECK_THROWS_WITH(it1_c > it2_c, "cannot compare order of object iterators"); + CHECK_THROWS_WITH(it2_c > it3_c, "cannot compare order of object iterators"); + CHECK_THROWS_WITH(it1_c > it3_c, "cannot compare order of object iterators"); } else { @@ -5452,6 +5887,14 @@ TEST_CASE("iterators") CHECK_THROWS_AS(it1_c >= it2_c, std::domain_error); CHECK_THROWS_AS(it2_c >= it3_c, std::domain_error); CHECK_THROWS_AS(it1_c >= it3_c, std::domain_error); + CHECK_THROWS_WITH(it1 >= it1, "cannot compare order of object iterators"); + CHECK_THROWS_WITH(it1 >= it2, "cannot compare order of object iterators"); + CHECK_THROWS_WITH(it2 >= it3, "cannot compare order of object iterators"); + CHECK_THROWS_WITH(it1 >= it3, "cannot compare order of object iterators"); + CHECK_THROWS_WITH(it1_c >= it1_c, "cannot compare order of object iterators"); + CHECK_THROWS_WITH(it1_c >= it2_c, "cannot compare order of object iterators"); + CHECK_THROWS_WITH(it2_c >= it3_c, "cannot compare order of object iterators"); + CHECK_THROWS_WITH(it1_c >= it3_c, "cannot compare order of object iterators"); } else { @@ -5477,9 +5920,13 @@ TEST_CASE("iterators") { CHECK_THROWS_AS(j.begin() == k.begin(), std::domain_error); CHECK_THROWS_AS(j.cbegin() == k.cbegin(), std::domain_error); + CHECK_THROWS_WITH(j.begin() == k.begin(), "cannot compare iterators of different containers"); + CHECK_THROWS_WITH(j.cbegin() == k.cbegin(), "cannot compare iterators of different containers"); CHECK_THROWS_AS(j.begin() < k.begin(), std::domain_error); CHECK_THROWS_AS(j.cbegin() < k.cbegin(), std::domain_error); + CHECK_THROWS_WITH(j.begin() < k.begin(), "cannot compare iterators of different containers"); + CHECK_THROWS_WITH(j.cbegin() < k.cbegin(), "cannot compare iterators of different containers"); } } } @@ -5499,42 +5946,52 @@ TEST_CASE("iterators") { auto it = j_object.begin(); CHECK_THROWS_AS(it += 1, std::domain_error); + CHECK_THROWS_WITH(it += 1, "cannot use offsets with object iterators"); } { auto it = j_object.cbegin(); CHECK_THROWS_AS(it += 1, std::domain_error); + CHECK_THROWS_WITH(it += 1, "cannot use offsets with object iterators"); } { auto it = j_object.begin(); CHECK_THROWS_AS(it + 1, std::domain_error); + CHECK_THROWS_WITH(it + 1, "cannot use offsets with object iterators"); } { auto it = j_object.cbegin(); CHECK_THROWS_AS(it + 1, std::domain_error); + CHECK_THROWS_WITH(it + 1, "cannot use offsets with object iterators"); } { auto it = j_object.begin(); CHECK_THROWS_AS(it -= 1, std::domain_error); + CHECK_THROWS_WITH(it -= 1, "cannot use offsets with object iterators"); } { auto it = j_object.cbegin(); CHECK_THROWS_AS(it -= 1, std::domain_error); + CHECK_THROWS_WITH(it -= 1, "cannot use offsets with object iterators"); } { auto it = j_object.begin(); CHECK_THROWS_AS(it - 1, std::domain_error); + CHECK_THROWS_WITH(it - 1, "cannot use offsets with object iterators"); } { auto it = j_object.cbegin(); CHECK_THROWS_AS(it - 1, std::domain_error); + CHECK_THROWS_WITH(it - 1, "cannot use offsets with object iterators"); } { auto it = j_object.begin(); CHECK_THROWS_AS(it - it, std::domain_error); + CHECK_THROWS_WITH(it - it, "cannot use offsets with object iterators"); } { auto it = j_object.cbegin(); CHECK_THROWS_AS(it - it, std::domain_error); + CHECK_THROWS_WITH(it - it, "cannot use offsets with object iterators"); } } @@ -5619,11 +6076,15 @@ TEST_CASE("iterators") auto it = j_object.begin(); CHECK_THROWS_AS(it[0], std::domain_error); CHECK_THROWS_AS(it[1], std::domain_error); + CHECK_THROWS_WITH(it[0], "cannot use operator[] for object iterators"); + CHECK_THROWS_WITH(it[1], "cannot use operator[] for object iterators"); } { auto it = j_object.cbegin(); CHECK_THROWS_AS(it[0], std::domain_error); CHECK_THROWS_AS(it[1], std::domain_error); + CHECK_THROWS_WITH(it[0], "cannot use operator[] for object iterators"); + CHECK_THROWS_WITH(it[1], "cannot use operator[] for object iterators"); } } @@ -5655,11 +6116,15 @@ TEST_CASE("iterators") auto it = j_null.begin(); CHECK_THROWS_AS(it[0], std::out_of_range); CHECK_THROWS_AS(it[1], std::out_of_range); + CHECK_THROWS_WITH(it[0], "cannot get value"); + CHECK_THROWS_WITH(it[1], "cannot get value"); } { auto it = j_null.cbegin(); CHECK_THROWS_AS(it[0], std::out_of_range); CHECK_THROWS_AS(it[1], std::out_of_range); + CHECK_THROWS_WITH(it[0], "cannot get value"); + CHECK_THROWS_WITH(it[1], "cannot get value"); } } @@ -5669,11 +6134,13 @@ TEST_CASE("iterators") auto it = j_value.begin(); CHECK(it[0] == json(42)); CHECK_THROWS_AS(it[1], std::out_of_range); + CHECK_THROWS_WITH(it[1], "cannot get value"); } { auto it = j_value.cbegin(); CHECK(it[0] == json(42)); CHECK_THROWS_AS(it[1], std::out_of_range); + CHECK_THROWS_WITH(it[1], "cannot get value"); } } } @@ -5735,6 +6202,14 @@ TEST_CASE("iterators") CHECK_THROWS_AS(it1_c < it2_c, std::domain_error); CHECK_THROWS_AS(it2_c < it3_c, std::domain_error); CHECK_THROWS_AS(it1_c < it3_c, std::domain_error); + CHECK_THROWS_WITH(it1 < it1, "cannot compare order of object iterators"); + CHECK_THROWS_WITH(it1 < it2, "cannot compare order of object iterators"); + CHECK_THROWS_WITH(it2 < it3, "cannot compare order of object iterators"); + CHECK_THROWS_WITH(it1 < it3, "cannot compare order of object iterators"); + CHECK_THROWS_WITH(it1_c < it1_c, "cannot compare order of object iterators"); + CHECK_THROWS_WITH(it1_c < it2_c, "cannot compare order of object iterators"); + CHECK_THROWS_WITH(it2_c < it3_c, "cannot compare order of object iterators"); + CHECK_THROWS_WITH(it1_c < it3_c, "cannot compare order of object iterators"); } else { @@ -5761,6 +6236,14 @@ TEST_CASE("iterators") CHECK_THROWS_AS(it1_c <= it2_c, std::domain_error); CHECK_THROWS_AS(it2_c <= it3_c, std::domain_error); CHECK_THROWS_AS(it1_c <= it3_c, std::domain_error); + CHECK_THROWS_WITH(it1 <= it1, "cannot compare order of object iterators"); + CHECK_THROWS_WITH(it1 <= it2, "cannot compare order of object iterators"); + CHECK_THROWS_WITH(it2 <= it3, "cannot compare order of object iterators"); + CHECK_THROWS_WITH(it1 <= it3, "cannot compare order of object iterators"); + CHECK_THROWS_WITH(it1_c <= it1_c, "cannot compare order of object iterators"); + CHECK_THROWS_WITH(it1_c <= it2_c, "cannot compare order of object iterators"); + CHECK_THROWS_WITH(it2_c <= it3_c, "cannot compare order of object iterators"); + CHECK_THROWS_WITH(it1_c <= it3_c, "cannot compare order of object iterators"); } else { @@ -5788,6 +6271,14 @@ TEST_CASE("iterators") CHECK_THROWS_AS(it1_c > it2_c, std::domain_error); CHECK_THROWS_AS(it2_c > it3_c, std::domain_error); CHECK_THROWS_AS(it1_c > it3_c, std::domain_error); + CHECK_THROWS_WITH(it1 > it1, "cannot compare order of object iterators"); + CHECK_THROWS_WITH(it1 > it2, "cannot compare order of object iterators"); + CHECK_THROWS_WITH(it2 > it3, "cannot compare order of object iterators"); + CHECK_THROWS_WITH(it1 > it3, "cannot compare order of object iterators"); + CHECK_THROWS_WITH(it1_c > it1_c, "cannot compare order of object iterators"); + CHECK_THROWS_WITH(it1_c > it2_c, "cannot compare order of object iterators"); + CHECK_THROWS_WITH(it2_c > it3_c, "cannot compare order of object iterators"); + CHECK_THROWS_WITH(it1_c > it3_c, "cannot compare order of object iterators"); } else { @@ -5815,6 +6306,14 @@ TEST_CASE("iterators") CHECK_THROWS_AS(it1_c >= it2_c, std::domain_error); CHECK_THROWS_AS(it2_c >= it3_c, std::domain_error); CHECK_THROWS_AS(it1_c >= it3_c, std::domain_error); + CHECK_THROWS_WITH(it1 >= it1, "cannot compare order of object iterators"); + CHECK_THROWS_WITH(it1 >= it2, "cannot compare order of object iterators"); + CHECK_THROWS_WITH(it2 >= it3, "cannot compare order of object iterators"); + CHECK_THROWS_WITH(it1 >= it3, "cannot compare order of object iterators"); + CHECK_THROWS_WITH(it1_c >= it1_c, "cannot compare order of object iterators"); + CHECK_THROWS_WITH(it1_c >= it2_c, "cannot compare order of object iterators"); + CHECK_THROWS_WITH(it2_c >= it3_c, "cannot compare order of object iterators"); + CHECK_THROWS_WITH(it1_c >= it3_c, "cannot compare order of object iterators"); } else { @@ -5840,9 +6339,13 @@ TEST_CASE("iterators") { CHECK_THROWS_AS(j.rbegin() == k.rbegin(), std::domain_error); CHECK_THROWS_AS(j.crbegin() == k.crbegin(), std::domain_error); + CHECK_THROWS_WITH(j.rbegin() == k.rbegin(), "cannot compare iterators of different containers"); + CHECK_THROWS_WITH(j.crbegin() == k.crbegin(), "cannot compare iterators of different containers"); CHECK_THROWS_AS(j.rbegin() < k.rbegin(), std::domain_error); CHECK_THROWS_AS(j.crbegin() < k.crbegin(), std::domain_error); + CHECK_THROWS_WITH(j.rbegin() < k.rbegin(), "cannot compare iterators of different containers"); + CHECK_THROWS_WITH(j.crbegin() < k.crbegin(), "cannot compare iterators of different containers"); } } } @@ -5862,42 +6365,52 @@ TEST_CASE("iterators") { auto it = j_object.rbegin(); CHECK_THROWS_AS(it += 1, std::domain_error); + CHECK_THROWS_WITH(it += 1, "cannot use offsets with object iterators"); } { auto it = j_object.crbegin(); CHECK_THROWS_AS(it += 1, std::domain_error); + CHECK_THROWS_WITH(it += 1, "cannot use offsets with object iterators"); } { auto it = j_object.rbegin(); CHECK_THROWS_AS(it + 1, std::domain_error); + CHECK_THROWS_WITH(it + 1, "cannot use offsets with object iterators"); } { auto it = j_object.crbegin(); CHECK_THROWS_AS(it + 1, std::domain_error); + CHECK_THROWS_WITH(it + 1, "cannot use offsets with object iterators"); } { auto it = j_object.rbegin(); CHECK_THROWS_AS(it -= 1, std::domain_error); + CHECK_THROWS_WITH(it -= 1, "cannot use offsets with object iterators"); } { auto it = j_object.crbegin(); CHECK_THROWS_AS(it -= 1, std::domain_error); + CHECK_THROWS_WITH(it -= 1, "cannot use offsets with object iterators"); } { auto it = j_object.rbegin(); CHECK_THROWS_AS(it - 1, std::domain_error); + CHECK_THROWS_WITH(it - 1, "cannot use offsets with object iterators"); } { auto it = j_object.crbegin(); CHECK_THROWS_AS(it - 1, std::domain_error); + CHECK_THROWS_WITH(it - 1, "cannot use offsets with object iterators"); } { auto it = j_object.rbegin(); CHECK_THROWS_AS(it - it, std::domain_error); + CHECK_THROWS_WITH(it - it, "cannot use offsets with object iterators"); } { auto it = j_object.crbegin(); CHECK_THROWS_AS(it - it, std::domain_error); + CHECK_THROWS_WITH(it - it, "cannot use offsets with object iterators"); } } @@ -5982,11 +6495,15 @@ TEST_CASE("iterators") auto it = j_object.rbegin(); CHECK_THROWS_AS(it[0], std::domain_error); CHECK_THROWS_AS(it[1], std::domain_error); + CHECK_THROWS_WITH(it[0], "cannot use offsets with object iterators"); + CHECK_THROWS_WITH(it[1], "cannot use offsets with object iterators"); } { auto it = j_object.crbegin(); CHECK_THROWS_AS(it[0], std::domain_error); CHECK_THROWS_AS(it[1], std::domain_error); + CHECK_THROWS_WITH(it[0], "cannot use offsets with object iterators"); + CHECK_THROWS_WITH(it[1], "cannot use offsets with object iterators"); } } @@ -6018,11 +6535,15 @@ TEST_CASE("iterators") auto it = j_null.rbegin(); CHECK_THROWS_AS(it[0], std::out_of_range); CHECK_THROWS_AS(it[1], std::out_of_range); + CHECK_THROWS_WITH(it[0], "cannot get value"); + CHECK_THROWS_WITH(it[1], "cannot get value"); } { auto it = j_null.crbegin(); CHECK_THROWS_AS(it[0], std::out_of_range); CHECK_THROWS_AS(it[1], std::out_of_range); + CHECK_THROWS_WITH(it[0], "cannot get value"); + CHECK_THROWS_WITH(it[1], "cannot get value"); } } @@ -6032,11 +6553,13 @@ TEST_CASE("iterators") auto it = j_value.rbegin(); CHECK(it[0] == json(42)); CHECK_THROWS_AS(it[1], std::out_of_range); + CHECK_THROWS_WITH(it[1], "cannot get value"); } { auto it = j_value.crbegin(); CHECK(it[0] == json(42)); CHECK_THROWS_AS(it[1], std::out_of_range); + CHECK_THROWS_WITH(it[1], "cannot get value"); } } } @@ -6637,6 +7160,7 @@ TEST_CASE("modifiers") { json j = 1; CHECK_THROWS_AS(j.push_back("Hello"), std::domain_error); + CHECK_THROWS_WITH(j.push_back("Hello"), "cannot use push_back() with number"); } } @@ -6666,6 +7190,7 @@ TEST_CASE("modifiers") json j = 1; json k("Hello"); CHECK_THROWS_AS(j.push_back(k), std::domain_error); + CHECK_THROWS_WITH(j.push_back(k), "cannot use push_back() with number"); } } } @@ -6698,6 +7223,8 @@ TEST_CASE("modifiers") json j = 1; json k("Hello"); CHECK_THROWS_AS(j.push_back(json::object_t::value_type({"one", 1})), std::domain_error); + CHECK_THROWS_WITH(j.push_back(json::object_t::value_type({"one", 1})), + "cannot use push_back() with number"); } } } @@ -6729,6 +7256,7 @@ TEST_CASE("modifiers") { json j = 1; CHECK_THROWS_AS(j += "Hello", std::domain_error); + CHECK_THROWS_WITH(j += "Hello", "cannot use push_back() with number"); } } @@ -6758,6 +7286,7 @@ TEST_CASE("modifiers") json j = 1; json k("Hello"); CHECK_THROWS_AS(j += k, std::domain_error); + CHECK_THROWS_WITH(j += k, "cannot use push_back() with number"); } } } @@ -6790,6 +7319,8 @@ TEST_CASE("modifiers") json j = 1; json k("Hello"); CHECK_THROWS_AS(j += json::object_t::value_type({"one", 1}), std::domain_error); + CHECK_THROWS_WITH(j += json::object_t::value_type({"one", 1}), + "cannot use push_back() with number"); } } } @@ -6926,6 +7457,11 @@ TEST_CASE("modifiers") CHECK_THROWS_AS(j_array.insert(j_array.end(), j_array.begin(), j_array.end()), std::domain_error); CHECK_THROWS_AS(j_array.insert(j_array.end(), j_other_array.begin(), j_other_array2.end()), std::domain_error); + + CHECK_THROWS_WITH(j_array.insert(j_array.end(), j_array.begin(), j_array.end()), + "passed iterators may not belong to container"); + CHECK_THROWS_WITH(j_array.insert(j_array.end(), j_other_array.begin(), j_other_array2.end()), + "iterators do not fit"); } } @@ -6970,6 +7506,16 @@ TEST_CASE("modifiers") CHECK_THROWS_AS(j_array.insert(j_another_array.end(), j_yet_another_array.begin(), j_yet_another_array.end()), std::domain_error); CHECK_THROWS_AS(j_array.insert(j_another_array.end(), {1, 2, 3, 4}), std::domain_error); + + CHECK_THROWS_WITH(j_array.insert(j_another_array.end(), 10), "iterator does not fit current value"); + CHECK_THROWS_WITH(j_array.insert(j_another_array.end(), j_value), + "iterator does not fit current value"); + CHECK_THROWS_WITH(j_array.insert(j_another_array.end(), 10, 11), + "iterator does not fit current value"); + CHECK_THROWS_WITH(j_array.insert(j_another_array.end(), j_yet_another_array.begin(), + j_yet_another_array.end()), "iterator does not fit current value"); + CHECK_THROWS_WITH(j_array.insert(j_another_array.end(), {1, 2, 3, 4}), + "iterator does not fit current value"); } SECTION("non-array type") @@ -6983,6 +7529,14 @@ TEST_CASE("modifiers") CHECK_THROWS_AS(j_nonarray.insert(j_nonarray.end(), j_yet_another_array.begin(), j_yet_another_array.end()), std::domain_error); CHECK_THROWS_AS(j_nonarray.insert(j_nonarray.end(), {1, 2, 3, 4}), std::domain_error); + + CHECK_THROWS_WITH(j_nonarray.insert(j_nonarray.end(), 10), "cannot use insert() with number"); + CHECK_THROWS_WITH(j_nonarray.insert(j_nonarray.end(), j_value), "cannot use insert() with number"); + CHECK_THROWS_WITH(j_nonarray.insert(j_nonarray.end(), 10, 11), "cannot use insert() with number"); + CHECK_THROWS_WITH(j_nonarray.insert(j_nonarray.end(), j_yet_another_array.begin(), + j_yet_another_array.end()), "cannot use insert() with number"); + CHECK_THROWS_WITH(j_nonarray.insert(j_nonarray.end(), {1, 2, 3, 4}), + "cannot use insert() with number"); } } @@ -7035,6 +7589,7 @@ TEST_CASE("modifiers") json::array_t a = {"foo", "bar", "baz"}; CHECK_THROWS_AS(j.swap(a), std::domain_error); + CHECK_THROWS_WITH(j.swap(a), "cannot use swap() with number"); } } @@ -7060,6 +7615,7 @@ TEST_CASE("modifiers") json::object_t o = {{"cow", "Kuh"}, {"chicken", "Huhn"}}; CHECK_THROWS_AS(j.swap(o), std::domain_error); + CHECK_THROWS_WITH(j.swap(o), "cannot use swap() with number"); } } @@ -7085,6 +7641,7 @@ TEST_CASE("modifiers") json::string_t s = "Hallo Welt"; CHECK_THROWS_AS(j.swap(s), std::domain_error); + CHECK_THROWS_WITH(j.swap(s), "cannot use swap() with number"); } } } @@ -7469,6 +8026,7 @@ TEST_CASE("iterator class") json j(json::value_t::null); json::iterator it = j.begin(); CHECK_THROWS_AS(*it, std::out_of_range); + CHECK_THROWS_WITH(*it, "cannot get value"); } SECTION("number") @@ -7478,6 +8036,7 @@ TEST_CASE("iterator class") CHECK(*it == json(17)); it = j.end(); CHECK_THROWS_AS(*it, std::out_of_range); + CHECK_THROWS_WITH(*it, "cannot get value"); } SECTION("object") @@ -7502,6 +8061,7 @@ TEST_CASE("iterator class") json j(json::value_t::null); json::iterator it = j.begin(); CHECK_THROWS_AS(it->type_name(), std::out_of_range); + CHECK_THROWS_WITH(it->type_name(), "cannot get value"); } SECTION("number") @@ -7511,6 +8071,7 @@ TEST_CASE("iterator class") CHECK(it->type_name() == "number"); it = j.end(); CHECK_THROWS_AS(it->type_name(), std::out_of_range); + CHECK_THROWS_WITH(it->type_name(), "cannot get value"); } SECTION("object") @@ -7833,6 +8394,7 @@ TEST_CASE("const_iterator class") json j(json::value_t::null); json::const_iterator it = j.cbegin(); CHECK_THROWS_AS(*it, std::out_of_range); + CHECK_THROWS_WITH(*it, "cannot get value"); } SECTION("number") @@ -7842,6 +8404,7 @@ TEST_CASE("const_iterator class") CHECK(*it == json(17)); it = j.cend(); CHECK_THROWS_AS(*it, std::out_of_range); + CHECK_THROWS_WITH(*it, "cannot get value"); } SECTION("object") @@ -7866,6 +8429,7 @@ TEST_CASE("const_iterator class") json j(json::value_t::null); json::const_iterator it = j.cbegin(); CHECK_THROWS_AS(it->type_name(), std::out_of_range); + CHECK_THROWS_WITH(it->type_name(), "cannot get value"); } SECTION("number") @@ -7875,6 +8439,7 @@ TEST_CASE("const_iterator class") CHECK(it->type_name() == "number"); it = j.cend(); CHECK_THROWS_AS(it->type_name(), std::out_of_range); + CHECK_THROWS_WITH(it->type_name(), "cannot get value"); } SECTION("object") @@ -8209,14 +8774,14 @@ TEST_CASE("lexer class") CHECK(json::lexer::token_type_name(json::lexer::token_type::literal_null) == "null literal"); CHECK(json::lexer::token_type_name(json::lexer::token_type::value_string) == "string literal"); CHECK(json::lexer::token_type_name(json::lexer::token_type::value_number) == "number literal"); - CHECK(json::lexer::token_type_name(json::lexer::token_type::begin_array) == "["); - CHECK(json::lexer::token_type_name(json::lexer::token_type::begin_object) == "{"); - CHECK(json::lexer::token_type_name(json::lexer::token_type::end_array) == "]"); - CHECK(json::lexer::token_type_name(json::lexer::token_type::end_object) == "}"); - CHECK(json::lexer::token_type_name(json::lexer::token_type::name_separator) == ":"); - CHECK(json::lexer::token_type_name(json::lexer::token_type::value_separator) == ","); + CHECK(json::lexer::token_type_name(json::lexer::token_type::begin_array) == "'['"); + CHECK(json::lexer::token_type_name(json::lexer::token_type::begin_object) == "'{'"); + CHECK(json::lexer::token_type_name(json::lexer::token_type::end_array) == "']'"); + CHECK(json::lexer::token_type_name(json::lexer::token_type::end_object) == "'}'"); + CHECK(json::lexer::token_type_name(json::lexer::token_type::name_separator) == "':'"); + CHECK(json::lexer::token_type_name(json::lexer::token_type::value_separator) == "','"); CHECK(json::lexer::token_type_name(json::lexer::token_type::parse_error) == ""); - CHECK(json::lexer::token_type_name(json::lexer::token_type::end_of_input) == ""); + CHECK(json::lexer::token_type_name(json::lexer::token_type::end_of_input) == "end of input"); } SECTION("parse errors on first character") @@ -8273,6 +8838,7 @@ TEST_CASE("lexer class") { CHECK(json::lexer::to_unicode(0x1F4A9) == "💩"); CHECK_THROWS_AS(json::lexer::to_unicode(0x200000), std::out_of_range); + CHECK_THROWS_WITH(json::lexer::to_unicode(0x200000), "code points above 0x10FFFF are invalid"); } } @@ -8332,11 +8898,15 @@ TEST_CASE("parser class") { // error: tab in string CHECK_THROWS_AS(json::parser("\"\t\"").parse(), std::invalid_argument); + CHECK_THROWS_WITH(json::parser("\"\t\"").parse(), "parse error - unexpected '\"'"); // error: newline in string CHECK_THROWS_AS(json::parser("\"\n\"").parse(), std::invalid_argument); CHECK_THROWS_AS(json::parser("\"\r\"").parse(), std::invalid_argument); + CHECK_THROWS_WITH(json::parser("\"\n\"").parse(), "parse error - unexpected '\"'"); + CHECK_THROWS_WITH(json::parser("\"\r\"").parse(), "parse error - unexpected '\"'"); // error: backspace in string CHECK_THROWS_AS(json::parser("\"\b\"").parse(), std::invalid_argument); + CHECK_THROWS_WITH(json::parser("\"\b\"").parse(), "parse error - unexpected '\"'"); } SECTION("escaped") @@ -8473,6 +9043,35 @@ TEST_CASE("parser class") CHECK_THROWS_AS(json::parser("-0e0-:").parse(), std::invalid_argument); CHECK_THROWS_AS(json::parser("-0e-:").parse(), std::invalid_argument); CHECK_THROWS_AS(json::parser("-0f").parse(), std::invalid_argument); + + CHECK_THROWS_WITH(json::parser("01").parse(), "parse error - 0 is not a number"); + CHECK_THROWS_WITH(json::parser("--1").parse(), "parse error - unexpected '-'"); + CHECK_THROWS_WITH(json::parser("1.").parse(), "parse error - 1 is not a number"); + CHECK_THROWS_WITH(json::parser("1E").parse(), + "parse error - unexpected 'E'; expected end of input"); + CHECK_THROWS_WITH(json::parser("1E-").parse(), + "parse error - unexpected 'E'; expected end of input"); + CHECK_THROWS_WITH(json::parser("1.E1").parse(), "parse error - 1 is not a number"); + CHECK_THROWS_WITH(json::parser("-1E").parse(), + "parse error - unexpected 'E'; expected end of input"); + CHECK_THROWS_WITH(json::parser("-0E#").parse(), + "parse error - unexpected 'E'; expected end of input"); + CHECK_THROWS_WITH(json::parser("-0E-#").parse(), + "parse error - unexpected 'E'; expected end of input"); + CHECK_THROWS_WITH(json::parser("-0#").parse(), + "parse error - unexpected '#'; expected end of input"); + CHECK_THROWS_WITH(json::parser("-0.0:").parse(), + "parse error - unexpected ':'; expected end of input"); + CHECK_THROWS_WITH(json::parser("-0.0Z").parse(), + "parse error - unexpected 'Z'; expected end of input"); + CHECK_THROWS_WITH(json::parser("-0E123:").parse(), + "parse error - unexpected ':'; expected end of input"); + CHECK_THROWS_WITH(json::parser("-0e0-:").parse(), + "parse error - unexpected '-'; expected end of input"); + CHECK_THROWS_WITH(json::parser("-0e-:").parse(), + "parse error - unexpected 'e'; expected end of input"); + CHECK_THROWS_WITH(json::parser("-0f").parse(), + "parse error - unexpected 'f'; expected end of input"); } } } @@ -8494,47 +9093,120 @@ TEST_CASE("parser class") CHECK_THROWS_AS(json::parser("1E.").parse(), std::invalid_argument); CHECK_THROWS_AS(json::parser("1E/").parse(), std::invalid_argument); CHECK_THROWS_AS(json::parser("1E:").parse(), std::invalid_argument); + CHECK_THROWS_WITH(json::parser("0.").parse(), "parse error - 0 is not a number"); + CHECK_THROWS_WITH(json::parser("-").parse(), "parse error - unexpected '-'"); + CHECK_THROWS_WITH(json::parser("--").parse(), + "parse error - unexpected '-'"); + CHECK_THROWS_WITH(json::parser("-0.").parse(), + "parse error - -0 is not a number"); + CHECK_THROWS_WITH(json::parser("-.").parse(), + "parse error - unexpected '-'"); + CHECK_THROWS_WITH(json::parser("-:").parse(), + "parse error - unexpected '-'"); + CHECK_THROWS_WITH(json::parser("0.:").parse(), + "parse error - 0 is not a number"); + CHECK_THROWS_WITH(json::parser("e.").parse(), + "parse error - unexpected 'e'"); + CHECK_THROWS_WITH(json::parser("1e.").parse(), + "parse error - unexpected 'e'; expected end of input"); + CHECK_THROWS_WITH(json::parser("1e/").parse(), + "parse error - unexpected 'e'; expected end of input"); + CHECK_THROWS_WITH(json::parser("1e:").parse(), + "parse error - unexpected 'e'; expected end of input"); + CHECK_THROWS_WITH(json::parser("1E.").parse(), + "parse error - unexpected 'E'; expected end of input"); + CHECK_THROWS_WITH(json::parser("1E/").parse(), + "parse error - unexpected 'E'; expected end of input"); + CHECK_THROWS_WITH(json::parser("1E:").parse(), + "parse error - unexpected 'E'; expected end of input"); // unexpected end of null CHECK_THROWS_AS(json::parser("n").parse(), std::invalid_argument); - CHECK_THROWS_AS(json::parser("n").parse(), std::invalid_argument); CHECK_THROWS_AS(json::parser("nu").parse(), std::invalid_argument); CHECK_THROWS_AS(json::parser("nul").parse(), std::invalid_argument); + CHECK_THROWS_WITH(json::parser("n").parse(), "parse error - unexpected 'n'"); + CHECK_THROWS_WITH(json::parser("nu").parse(), + "parse error - unexpected 'n'"); + CHECK_THROWS_WITH(json::parser("nul").parse(), + "parse error - unexpected 'n'"); // unexpected end of true CHECK_THROWS_AS(json::parser("t").parse(), std::invalid_argument); - CHECK_THROWS_AS(json::parser("t").parse(), std::invalid_argument); CHECK_THROWS_AS(json::parser("tr").parse(), std::invalid_argument); CHECK_THROWS_AS(json::parser("tru").parse(), std::invalid_argument); + CHECK_THROWS_WITH(json::parser("t").parse(), "parse error - unexpected 't'"); + CHECK_THROWS_WITH(json::parser("tr").parse(), + "parse error - unexpected 't'"); + CHECK_THROWS_WITH(json::parser("tru").parse(), + "parse error - unexpected 't'"); // unexpected end of false CHECK_THROWS_AS(json::parser("f").parse(), std::invalid_argument); CHECK_THROWS_AS(json::parser("fa").parse(), std::invalid_argument); CHECK_THROWS_AS(json::parser("fal").parse(), std::invalid_argument); CHECK_THROWS_AS(json::parser("fals").parse(), std::invalid_argument); + CHECK_THROWS_WITH(json::parser("f").parse(), "parse error - unexpected 'f'"); + CHECK_THROWS_WITH(json::parser("fa").parse(), + "parse error - unexpected 'f'"); + CHECK_THROWS_WITH(json::parser("fal").parse(), + "parse error - unexpected 'f'"); + CHECK_THROWS_WITH(json::parser("fals").parse(), + "parse error - unexpected 'f'"); - // unexpected end of array + // missing/unexpected end of array CHECK_THROWS_AS(json::parser("[").parse(), std::invalid_argument); CHECK_THROWS_AS(json::parser("[1").parse(), std::invalid_argument); CHECK_THROWS_AS(json::parser("[1,").parse(), std::invalid_argument); CHECK_THROWS_AS(json::parser("[1,]").parse(), std::invalid_argument); CHECK_THROWS_AS(json::parser("]").parse(), std::invalid_argument); + CHECK_THROWS_WITH(json::parser("[").parse(), + "parse error - unexpected end of input"); + CHECK_THROWS_WITH(json::parser("[1").parse(), + "parse error - unexpected end of input; expected ']'"); + CHECK_THROWS_WITH(json::parser("[1,").parse(), + "parse error - unexpected end of input"); + CHECK_THROWS_WITH(json::parser("[1,]").parse(), + "parse error - unexpected ']'"); + CHECK_THROWS_WITH(json::parser("]").parse(), "parse error - unexpected ']'"); - // unexpected end of object + // missing/unexpected end of object CHECK_THROWS_AS(json::parser("{").parse(), std::invalid_argument); CHECK_THROWS_AS(json::parser("{\"foo\"").parse(), std::invalid_argument); CHECK_THROWS_AS(json::parser("{\"foo\":").parse(), std::invalid_argument); CHECK_THROWS_AS(json::parser("{\"foo\":}").parse(), std::invalid_argument); CHECK_THROWS_AS(json::parser("{\"foo\":1,}").parse(), std::invalid_argument); CHECK_THROWS_AS(json::parser("}").parse(), std::invalid_argument); + CHECK_THROWS_WITH(json::parser("{").parse(), + "parse error - unexpected end of input; expected string literal"); + CHECK_THROWS_WITH(json::parser("{\"foo\"").parse(), + "parse error - unexpected end of input; expected ':'"); + CHECK_THROWS_WITH(json::parser("{\"foo\":").parse(), + "parse error - unexpected end of input"); + CHECK_THROWS_WITH(json::parser("{\"foo\":}").parse(), + "parse error - unexpected '}'"); + CHECK_THROWS_WITH(json::parser("{\"foo\":1,}").parse(), + "parse error - unexpected '}'; expected string literal"); + CHECK_THROWS_WITH(json::parser("}").parse(), "parse error - unexpected '}'"); - // unexpected end of string + // missing/unexpected end of string CHECK_THROWS_AS(json::parser("\"").parse(), std::invalid_argument); CHECK_THROWS_AS(json::parser("\"\\\"").parse(), std::invalid_argument); CHECK_THROWS_AS(json::parser("\"\\u\"").parse(), std::invalid_argument); CHECK_THROWS_AS(json::parser("\"\\u0\"").parse(), std::invalid_argument); CHECK_THROWS_AS(json::parser("\"\\u01\"").parse(), std::invalid_argument); CHECK_THROWS_AS(json::parser("\"\\u012\"").parse(), std::invalid_argument); + CHECK_THROWS_WITH(json::parser("\"").parse(), + "parse error - unexpected '\"'"); + CHECK_THROWS_WITH(json::parser("\"\\\"").parse(), + "parse error - unexpected '\"'"); + CHECK_THROWS_WITH(json::parser("\"\\u\"").parse(), + "parse error - unexpected '\"'"); + CHECK_THROWS_WITH(json::parser("\"\\u0\"").parse(), + "parse error - unexpected '\"'"); + CHECK_THROWS_WITH(json::parser("\"\\u01\"").parse(), + "parse error - unexpected '\"'"); + CHECK_THROWS_WITH(json::parser("\"\\u012\"").parse(), + "parse error - unexpected '\"'"); // invalid escapes for (int c = 1; c < 128; ++c) @@ -8567,6 +9239,7 @@ TEST_CASE("parser class") default: { CHECK_THROWS_AS(json::parser(s).parse(), std::invalid_argument); + CHECK_THROWS_WITH(json::parser(s).parse(), "parse error - unexpected '\"'"); break; } } @@ -8635,16 +9308,28 @@ TEST_CASE("parser class") CHECK_THROWS_AS(json::parser(s2).parse(), std::invalid_argument); CHECK_THROWS_AS(json::parser(s3).parse(), std::invalid_argument); CHECK_THROWS_AS(json::parser(s4).parse(), std::invalid_argument); + + CHECK_THROWS_WITH(json::parser(s1).parse(), "parse error - unexpected '\"'"); + CHECK_THROWS_WITH(json::parser(s2).parse(), "parse error - unexpected '\"'"); + CHECK_THROWS_WITH(json::parser(s3).parse(), "parse error - unexpected '\"'"); + CHECK_THROWS_WITH(json::parser(s4).parse(), "parse error - unexpected '\"'"); } } } // missing part of a surrogate pair CHECK_THROWS_AS(json::parse("\"\\uD80C\""), std::invalid_argument); + CHECK_THROWS_WITH(json::parse("\"\\uD80C\""), "missing low surrogate"); // invalid surrogate pair CHECK_THROWS_AS(json::parse("\"\\uD80C\\uD80C\""), std::invalid_argument); CHECK_THROWS_AS(json::parse("\"\\uD80C\\u0000\""), std::invalid_argument); CHECK_THROWS_AS(json::parse("\"\\uD80C\\uFFFF\""), std::invalid_argument); + CHECK_THROWS_WITH(json::parse("\"\\uD80C\\uD80C\""), + "missing or wrong low surrogate"); + CHECK_THROWS_WITH(json::parse("\"\\uD80C\\u0000\""), + "missing or wrong low surrogate"); + CHECK_THROWS_WITH(json::parse("\"\\uD80C\\uFFFF\""), + "missing or wrong low surrogate"); } SECTION("callback function") @@ -9237,6 +9922,7 @@ TEST_CASE("algorithms") { json j({{"one", 1}, {"two", 2}}); CHECK_THROWS_AS(std::sort(j.begin(), j.end()), std::domain_error); + CHECK_THROWS_WITH(std::sort(j.begin(), j.end()), "cannot use offsets with object iterators"); } } @@ -9414,7 +10100,7 @@ TEST_CASE("concepts") SECTION("CopyAssignable") { -// STL iterators used by json::iterator don't pass this test in Debug mode + // STL iterators used by json::iterator don't pass this test in Debug mode #if !defined(_MSC_VER) || (_ITERATOR_DEBUG_LEVEL == 0) CHECK(std::is_nothrow_copy_assignable::value); CHECK(std::is_nothrow_copy_assignable::value); @@ -9475,6 +10161,11 @@ TEST_CASE("iterator_wrapper") CHECK(i.value() == json(2)); break; } + + default: + { + break; + } } } @@ -9494,6 +10185,10 @@ TEST_CASE("iterator_wrapper") { CHECK(i.key() == "A"); CHECK(i.value() == json(1)); + + // change the value + i.value() = json(11); + CHECK(i.value() == json(11)); break; } @@ -9501,12 +10196,24 @@ TEST_CASE("iterator_wrapper") { CHECK(i.key() == "B"); CHECK(i.value() == json(2)); + + // change the value + i.value() = json(22); + CHECK(i.value() == json(22)); + break; + } + + default: + { break; } } } CHECK(counter == 3); + + // check if values where changed + CHECK(j == json({{"A", 11}, {"B", 22}})); } SECTION("const value") @@ -9531,6 +10238,11 @@ TEST_CASE("iterator_wrapper") CHECK(i.value() == json(2)); break; } + + default: + { + break; + } } } @@ -9559,6 +10271,146 @@ TEST_CASE("iterator_wrapper") CHECK(i.value() == json(2)); break; } + + default: + { + break; + } + } + } + + CHECK(counter == 3); + } + } + + SECTION("const object") + { + SECTION("value") + { + const json j = {{"A", 1}, {"B", 2}}; + int counter = 1; + + for (auto i : json::iterator_wrapper(j)) + { + switch (counter++) + { + case 1: + { + CHECK(i.key() == "A"); + CHECK(i.value() == json(1)); + break; + } + + case 2: + { + CHECK(i.key() == "B"); + CHECK(i.value() == json(2)); + break; + } + + default: + { + break; + } + } + } + + CHECK(counter == 3); + } + + SECTION("reference") + { + const json j = {{"A", 1}, {"B", 2}}; + int counter = 1; + + for (auto& i : json::iterator_wrapper(j)) + { + switch (counter++) + { + case 1: + { + CHECK(i.key() == "A"); + CHECK(i.value() == json(1)); + break; + } + + case 2: + { + CHECK(i.key() == "B"); + CHECK(i.value() == json(2)); + break; + } + + default: + { + break; + } + } + } + + CHECK(counter == 3); + } + + SECTION("const value") + { + const json j = {{"A", 1}, {"B", 2}}; + int counter = 1; + + for (const auto i : json::iterator_wrapper(j)) + { + switch (counter++) + { + case 1: + { + CHECK(i.key() == "A"); + CHECK(i.value() == json(1)); + break; + } + + case 2: + { + CHECK(i.key() == "B"); + CHECK(i.value() == json(2)); + break; + } + + default: + { + break; + } + } + } + + CHECK(counter == 3); + } + + SECTION("const reference") + { + const json j = {{"A", 1}, {"B", 2}}; + int counter = 1; + + for (const auto& i : json::iterator_wrapper(j)) + { + switch (counter++) + { + case 1: + { + CHECK(i.key() == "A"); + CHECK(i.value() == json(1)); + break; + } + + case 2: + { + CHECK(i.key() == "B"); + CHECK(i.value() == json(2)); + break; + } + + default: + { + break; + } } } @@ -9590,6 +10442,11 @@ TEST_CASE("iterator_wrapper") CHECK(i.value() == "B"); break; } + + default: + { + break; + } } } @@ -9609,6 +10466,10 @@ TEST_CASE("iterator_wrapper") { CHECK(i.key() == "0"); CHECK(i.value() == "A"); + + // change the value + i.value() = "AA"; + CHECK(i.value() == "AA"); break; } @@ -9616,12 +10477,24 @@ TEST_CASE("iterator_wrapper") { CHECK(i.key() == "1"); CHECK(i.value() == "B"); + + // change the value + i.value() = "BB"; + CHECK(i.value() == "BB"); + break; + } + + default: + { break; } } } CHECK(counter == 3); + + // check if values where changed + CHECK(j == json({"AA", "BB"})); } SECTION("const value") @@ -9646,6 +10519,11 @@ TEST_CASE("iterator_wrapper") CHECK(i.value() == "B"); break; } + + default: + { + break; + } } } @@ -9674,6 +10552,146 @@ TEST_CASE("iterator_wrapper") CHECK(i.value() == "B"); break; } + + default: + { + break; + } + } + } + + CHECK(counter == 3); + } + } + + SECTION("const array") + { + SECTION("value") + { + const json j = {"A", "B"}; + int counter = 1; + + for (auto i : json::iterator_wrapper(j)) + { + switch (counter++) + { + case 1: + { + CHECK(i.key() == "0"); + CHECK(i.value() == "A"); + break; + } + + case 2: + { + CHECK(i.key() == "1"); + CHECK(i.value() == "B"); + break; + } + + default: + { + break; + } + } + } + + CHECK(counter == 3); + } + + SECTION("reference") + { + const json j = {"A", "B"}; + int counter = 1; + + for (auto& i : json::iterator_wrapper(j)) + { + switch (counter++) + { + case 1: + { + CHECK(i.key() == "0"); + CHECK(i.value() == "A"); + break; + } + + case 2: + { + CHECK(i.key() == "1"); + CHECK(i.value() == "B"); + break; + } + + default: + { + break; + } + } + } + + CHECK(counter == 3); + } + + SECTION("const value") + { + const json j = {"A", "B"}; + int counter = 1; + + for (const auto i : json::iterator_wrapper(j)) + { + switch (counter++) + { + case 1: + { + CHECK(i.key() == "0"); + CHECK(i.value() == "A"); + break; + } + + case 2: + { + CHECK(i.key() == "1"); + CHECK(i.value() == "B"); + break; + } + + default: + { + break; + } + } + } + + CHECK(counter == 3); + } + + SECTION("const reference") + { + const json j = {"A", "B"}; + int counter = 1; + + for (const auto& i : json::iterator_wrapper(j)) + { + switch (counter++) + { + case 1: + { + CHECK(i.key() == "0"); + CHECK(i.value() == "A"); + break; + } + + case 2: + { + CHECK(i.key() == "1"); + CHECK(i.value() == "B"); + break; + } + + default: + { + break; + } } } @@ -9708,9 +10726,15 @@ TEST_CASE("iterator_wrapper") ++counter; CHECK(i.key() == ""); CHECK(i.value() == json(1)); + + // change value + i.value() = json(2); } CHECK(counter == 2); + + // check if value has changed + CHECK(j == json(2)); } SECTION("const value") @@ -9728,7 +10752,7 @@ TEST_CASE("iterator_wrapper") CHECK(counter == 2); } - SECTION("reference") + SECTION("const reference") { json j = 1; int counter = 1; @@ -9743,6 +10767,69 @@ TEST_CASE("iterator_wrapper") CHECK(counter == 2); } } + + SECTION("const primitive") + { + SECTION("value") + { + const json j = 1; + int counter = 1; + + for (auto i : json::iterator_wrapper(j)) + { + ++counter; + CHECK(i.key() == ""); + CHECK(i.value() == json(1)); + } + + CHECK(counter == 2); + } + + SECTION("reference") + { + const json j = 1; + int counter = 1; + + for (auto& i : json::iterator_wrapper(j)) + { + ++counter; + CHECK(i.key() == ""); + CHECK(i.value() == json(1)); + } + + CHECK(counter == 2); + } + + SECTION("const value") + { + const json j = 1; + int counter = 1; + + for (const auto i : json::iterator_wrapper(j)) + { + ++counter; + CHECK(i.key() == ""); + CHECK(i.value() == json(1)); + } + + CHECK(counter == 2); + } + + SECTION("const reference") + { + const json j = 1; + int counter = 1; + + for (const auto& i : json::iterator_wrapper(j)) + { + ++counter; + CHECK(i.key() == ""); + CHECK(i.value() == json(1)); + } + + CHECK(counter == 2); + } + } } TEST_CASE("compliance tests from json.org") @@ -10208,6 +11295,14 @@ TEST_CASE("Unicode", "[hide]") // the array has 1112064 + 1 elemnts (a terminating "null" value) CHECK(j.size() == 1112065); } + + SECTION("ignore byte-order-mark") + { + // read a file with a UTF-8 BOM + std::ifstream f("test/json_nlohmann_tests/bom.json"); + json j; + CHECK_NOTHROW(j << f); + } } TEST_CASE("regression tests") @@ -10391,4 +11486,22 @@ TEST_CASE("regression tests") j["string"] = bytes; CHECK(j["string"] == "\u0007\u0007"); } + + SECTION("issue #144 - implicit assignment to std::string fails") + { + json o = {{"name", "value"}}; + + std::string s1 = o["name"]; + CHECK(s1 == "value"); + + std::string s2; + s2 = o["name"]; + + CHECK(s2 == "value"); + } + + SECTION("character following a surrogate pair is skipped") + { + CHECK(json::parse("\"\\ud80c\\udc60abc\"").get() == u8"\U00013060abc"); + } }