From 662f5a771f3807bf7ebd5a3989230d5d0a26659d Mon Sep 17 00:00:00 2001 From: Toni Uhlig Date: Mon, 15 Nov 2021 12:51:20 +0100 Subject: Squashed 'deps/inja/' changes from 811e173..eb04bfc eb04bfc clang-format 81cfa14 init LiteralNode with string view, code cleaning 2da715a parse_filename without json 623c267 make_result and code cleaning 1206913 rename json -> data d38c07e clang 5 minimum a067a8b make all tests C++17 098de66 inja_test C++17 173c1f5 fix C++17 2239e23 remove string view polyfill fb55f2e update minimum compiler 751d27d fix MIT license 635e1fb change readme example 5a4ac92 update single include d038c53 set .cpp in readme cf71b54 add include callback 9b9dd96 introduce INJA_DATA_TYPE 12a2c9b unify json data type 4505fd0 add tests for assignments 2d51507 set version 3.3 2ba5783 Merge branch 'master' of github.com:pantor/inja 99b85d6 remove headers in files, add central header 14f0d38 Fixed an issue with round (#207) c70fd58 Merge branch 'master' of github.com:pantor/inja 8a9aee1 add jinja python test script 91c93bf add join function (#204) 9cf7db8 add warnings for benchmark, fix for inja_throw e91a2fd #ifndef INJA_THROW 8d65633 add warnings for clang and fix them eaec58d update nlohmann/json to master 4ccadd5 update to json=3.9.1, doctest=2.4.6 83feb26 update single include 4e90947 at function for objects b473873 compactify assignments readme 798a0b9 Add support for setting subobjects via JSON pointer in set statements. (#202) 86f38f0 update single include d9ad8d1 whitespace control for comments 95af782 Add Child Inheritance Feature (#198) b4b9d8d Fix build warnings for Clang and MSVC (#197) b14f8a1 add ci for gcc 10, 11 06ff271 add no excpetion test 2491980 Fix inclusion in translation units with exceptions disabled. (#196) 4d5a7d1 fix single include eac2162 fix include of in-memory templates ca3c7a0 add test for short circuit evaluation 389c1d6 short circuit evaluation git-subtree-dir: deps/inja git-subtree-split: eb04bfc7d3cf0a5fbd0ed37a88d064230d0af377 --- .clang-format | 11 +- .github/workflows/ci.yml | 79 +- .github/workflows/documentation.yml | 2 +- .github/workflows/single-include.yml | 6 +- .gitignore | 3 + CMakeLists.txt | 36 +- LICENSE | 31 +- README.md | 104 +- doc/Doxyfile | 2 +- include/inja/config.hpp | 11 +- include/inja/environment.hpp | 87 +- include/inja/exceptions.hpp | 21 +- include/inja/function_storage.hpp | 86 +- include/inja/inja.hpp | 48 +- include/inja/lexer.hpp | 77 +- include/inja/node.hpp | 262 +- include/inja/parser.hpp | 273 +- include/inja/renderer.hpp | 524 +- include/inja/statistics.hpp | 37 +- include/inja/string_view.hpp | 1416 ---- include/inja/template.hpp | 8 +- include/inja/token.hpp | 11 +- include/inja/utils.hpp | 40 +- single_include/inja/inja.hpp | 4526 ++++------- test/benchmark.cpp | 20 +- test/data/html-extend/base.txt | 11 + test/data/html-extend/data.json | 12 + test/data/html-extend/inter.txt | 3 + test/data/html-extend/result.txt | 11 + test/data/html-extend/template.txt | 7 + test/data/include-both.txt | 1 + test/jinja.py | 11 + test/templates/example.txt | 1 + test/test-files.cpp | 35 +- test/test-functions.cpp | 51 +- test/test-renderer.cpp | 79 +- test/test-units.cpp | 10 +- test/test.cpp | 2 - third_party/include/doctest/LICENSE.txt | 2 +- third_party/include/doctest/doctest.h | 1619 ++-- third_party/include/nlohmann/LICENSE.MIT | 2 +- third_party/include/nlohmann/json.hpp | 11652 ++++++++++++++++------------- 42 files changed, 10507 insertions(+), 10723 deletions(-) delete mode 100644 include/inja/string_view.hpp create mode 100644 test/data/html-extend/base.txt create mode 100644 test/data/html-extend/data.json create mode 100644 test/data/html-extend/inter.txt create mode 100644 test/data/html-extend/result.txt create mode 100644 test/data/html-extend/template.txt create mode 100755 test/data/include-both.txt create mode 100644 test/jinja.py create mode 100644 test/templates/example.txt diff --git a/.clang-format b/.clang-format index 5486ac5..3a48294 100644 --- a/.clang-format +++ b/.clang-format @@ -1,7 +1,14 @@ +--- +BasedOnStyle: LLVM +IndentWidth: 2 +ColumnLimit: 160 + --- Language: Cpp -BasedOnStyle: LLVM -ColumnLimit: 120 SpaceBeforeCpp11BracedList: true +PointerAlignment: Left +AllowShortFunctionsOnASingleLine: Empty +AllowShortBlocksOnASingleLine: Empty +SpaceBeforeCtorInitializerColon: false ... diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index c5909c3..c499221 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -16,24 +16,19 @@ jobs: fail-fast: false matrix: name: [ - ubuntu-18.04-gcc-4.9, - ubuntu-18.04-gcc-5, - ubuntu-18.04-gcc-6, ubuntu-18.04-gcc-7, ubuntu-18.04-gcc-8, ubuntu-18.04-gcc-9, - ubuntu-18.04-clang-3.5, - ubuntu-18.04-clang-3.8, - ubuntu-18.04-clang-4.0, + ubuntu-20.04-gcc-10, + ubuntu-20.04-gcc-11, ubuntu-18.04-clang-5.0, ubuntu-18.04-clang-6.0, ubuntu-18.04-clang-7, ubuntu-18.04-clang-8, ubuntu-20.04-clang-9, - windows-2016-cl, - windows-2016-clang-cl, - windows-2016-clang, - windows-2016-gcc, + ubuntu-20.04-clang-10, + ubuntu-20.04-clang-11, + ubuntu-20.04-clang-11-no-exceptions, windows-2019-cl, windows-2019-clang-cl, windows-2019-clang, @@ -46,21 +41,6 @@ jobs: ] include: - - name: ubuntu-18.04-gcc-4.9 - os: ubuntu-18.04 - compiler: gcc - version: "4.9" - - - name: ubuntu-18.04-gcc-5 - os: ubuntu-18.04 - compiler: gcc - version: "5" - - - name: ubuntu-18.04-gcc-6 - os: ubuntu-18.04 - compiler: gcc - version: "6" - - name: ubuntu-18.04-gcc-7 os: ubuntu-18.04 compiler: gcc @@ -76,20 +56,15 @@ jobs: compiler: gcc version: "9" - - name: ubuntu-18.04-clang-3.5 - os: ubuntu-18.04 - compiler: clang - version: "3.5" - - - name: ubuntu-18.04-clang-3.8 - os: ubuntu-18.04 - compiler: clang - version: "3.8" + - name: ubuntu-20.04-gcc-10 + os: ubuntu-20.04 + compiler: gcc + version: "10" - - name: ubuntu-18.04-clang-4.0 - os: ubuntu-18.04 - compiler: clang - version: "4.0" + - name: ubuntu-20.04-gcc-11 + os: ubuntu-20.04 + compiler: gcc + version: "11" - name: ubuntu-18.04-clang-5.0 os: ubuntu-18.04 @@ -116,26 +91,26 @@ jobs: compiler: clang version: "9" - - name: windows-2016-cl - os: windows-2016 - compiler: cl + - name: ubuntu-20.04-clang-10 + os: ubuntu-20.04 + compiler: clang + version: "10" - - name: windows-2016-clang-cl - os: windows-2016 - compiler: clang-cl + - name: ubuntu-20.04-clang-11 + os: ubuntu-20.04 + compiler: clang + version: "11" - - name: windows-2016-clang - os: windows-2016 + - name: ubuntu-20.04-clang-11-no-exceptions + os: ubuntu-20.04 compiler: clang + version: "11" + cmake_vars: "-DCMAKE_CXX_FLAGS=-fno-exceptions -DBUILD_TESTING=OFF -DBUILD_BENCHMARK=ON" - name: windows-2019-cl os: windows-2019 compiler: cl - - name: windows-2016-gcc - os: windows-2016 - compiler: gcc - - name: windows-2019-clang-cl os: windows-2019 compiler: clang-cl @@ -174,7 +149,7 @@ jobs: version: "12.2" steps: - - uses: actions/checkout@v1 + - uses: actions/checkout@v2 - name: Install (Linux) if: runner.os == 'Linux' @@ -256,6 +231,6 @@ jobs: - name: Build & Test Release run: | cmake -E remove_directory build - cmake -B build -S . -DCMAKE_BUILD_TYPE=Release + cmake -B build -S . -DCMAKE_BUILD_TYPE=Release ${{ matrix.cmake_vars }} cmake --build build -j2 cd build && ctest -j2 --output-on-failure diff --git a/.github/workflows/documentation.yml b/.github/workflows/documentation.yml index cddd438..e32cfeb 100644 --- a/.github/workflows/documentation.yml +++ b/.github/workflows/documentation.yml @@ -9,7 +9,7 @@ jobs: build-deploy: runs-on: ubuntu-latest steps: - - uses: actions/checkout@master + - uses: actions/checkout@v2 - name: dependencies env: diff --git a/.github/workflows/single-include.yml b/.github/workflows/single-include.yml index 44a5a66..a7b0a7f 100644 --- a/.github/workflows/single-include.yml +++ b/.github/workflows/single-include.yml @@ -6,7 +6,7 @@ jobs: test: runs-on: ubuntu-latest steps: - - uses: actions/checkout@master + - uses: actions/checkout@v2 - name: update single include run: | @@ -17,10 +17,6 @@ jobs: working-directory: ${{runner.workspace}}/inja/single_include/ shell: bash run: | - pwd - ls inja - cat inja/inja.hpp - cat inja/inja_old.hpp diff inja/inja.hpp inja/inja_old.hpp >/dev/null - uses: actions/upload-artifact@v1 diff --git a/.gitignore b/.gitignore index 060ca13..88d9a4d 100644 --- a/.gitignore +++ b/.gitignore @@ -36,8 +36,11 @@ dist .coveralls.yml .vscode +.vs doc/html doc/latex +__pycache__ + examples diff --git a/CMakeLists.txt b/CMakeLists.txt index 87691d4..772c9e0 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,7 +1,7 @@ cmake_minimum_required(VERSION 3.5) -project(inja LANGUAGES CXX VERSION 3.1.0) +project(inja LANGUAGES CXX VERSION 3.3.0) option(INJA_USE_EMBEDDED_JSON "Use the shipped json header if not available on the system" ON) @@ -12,30 +12,28 @@ option(INJA_BUILD_TESTS "Build unit tests when BUILD_TESTING is enabled." ON) option(BUILD_BENCHMARK "Build benchmark" ON) option(COVERALLS "Generate coveralls data" OFF) -set(INJA_INSTALL_INCLUDE_DIR "include") +set(INJA_INSTALL_INCLUDE_DIR "include") +set(INJA_PACKAGE_USE_EMBEDDED_JSON OFF) set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} ${PROJECT_SOURCE_DIR}/cmake) -if(CMAKE_COMPILER_IS_GNUCC) - add_compile_options(-Wall) -endif() +# For using the correct __cplusplus macro if(MSVC) - add_compile_options(/W4 /permissive- /utf-8 /Zc:__cplusplus) + add_compile_options(/utf-8 /Zc:__cplusplus) endif() add_library(inja INTERFACE) add_library(pantor::inja ALIAS inja) + target_include_directories(inja INTERFACE $ $ ) +target_compile_features(inja INTERFACE cxx_std_17) -target_compile_features(inja INTERFACE cxx_std_11) - -set(INJA_PACKAGE_USE_EMBEDDED_JSON OFF) if(INJA_USE_EMBEDDED_JSON) find_package(nlohmann_json QUIET) @@ -49,7 +47,7 @@ if(INJA_USE_EMBEDDED_JSON) $ ) - target_compile_features(nlohmann_json INTERFACE cxx_std_11) + target_compile_features(nlohmann_json INTERFACE cxx_std_17) install(TARGETS nlohmann_json EXPORT injaTargets) @@ -67,10 +65,11 @@ else() endif() endif() + target_link_libraries(inja INTERFACE ${INJA_SELECTED_JSON_LIBRARY}) + execute_process(COMMAND scripts/update_single_include.sh WORKING_DIRECTORY ${PROJECT_SOURCE_DIR}) -# CMake: add_custom_command if(BUILD_TESTING AND INJA_BUILD_TESTS) @@ -78,12 +77,17 @@ if(BUILD_TESTING AND INJA_BUILD_TESTS) add_executable(inja_test test/test.cpp) target_link_libraries(inja_test PRIVATE inja) - add_test(inja_test ${CMAKE_RUNTIME_OUTPUT_DIRECTORY}/inja_test) + if(MSVC) + target_compile_options(inja_test PRIVATE /W4 /permissive-) + else() + target_compile_options(inja_test PRIVATE -Wall -Wextra -pedantic) + endif() + add_library(single_inja INTERFACE) - target_compile_features(single_inja INTERFACE cxx_std_11) + target_compile_features(single_inja INTERFACE cxx_std_17) target_include_directories(single_inja INTERFACE single_include include third_party/include) add_executable(single_inja_test test/test.cpp) @@ -96,6 +100,12 @@ endif() if(BUILD_BENCHMARK) add_executable(inja_benchmark test/benchmark.cpp) target_link_libraries(inja_benchmark PRIVATE inja) + + if(MSVC) + target_compile_options(inja_benchmark PRIVATE /W4 /permissive-) + else() + target_compile_options(inja_benchmark PRIVATE -Wall -Wextra -pedantic) + endif() endif() diff --git a/LICENSE b/LICENSE index c6a3b44..9e06bea 100644 --- a/LICENSE +++ b/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2018 lbersch +Copyright (c) 2018-2021 Berscheid Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal @@ -19,32 +19,3 @@ 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. - - ---- - - -Copyright (c) 2009-2018 FIRST -All rights reserved. - -Redistribution and use in source and binary forms, with or without -modification, are permitted provided that the following conditions are met: - * Redistributions of source code must retain the above copyright - notice, this list of conditions and the following disclaimer. - * Redistributions in binary form must reproduce the above copyright - notice, this list of conditions and the following disclaimer in the - documentation and/or other materials provided with the distribution. - * Neither the name of the FIRST nor the - names of its contributors may be used to endorse or promote products - derived from this software without specific prior written permission. - -THIS SOFTWARE IS PROVIDED BY FIRST AND CONTRIBUTORS``AS IS'' AND ANY -EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED -WARRANTIES OF MERCHANTABILITY NONINFRINGEMENT AND FITNESS FOR A PARTICULAR -PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL FIRST OR CONTRIBUTORS BE LIABLE FOR -ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES -(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; -LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND -ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT -(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS -SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. \ No newline at end of file diff --git a/README.md b/README.md index 4c55556..aada14f 100644 --- a/README.md +++ b/README.md @@ -44,7 +44,6 @@ Inja is a headers only library, which can be downloaded from the [releases](http // Just for convenience using namespace inja; -using json = nlohmann::json; ``` If you are using the [Meson Build System](http://mesonbuild.com), then you can wrap this repository as a subproject. @@ -184,20 +183,30 @@ env.render("Content: {% include \"content\" %}", data); // "Content: Hello Peter // Other template files are included relative from the current file location render("{% include \"footer.html\" %}", data); +``` +If a corresponding template could not be found in the file system, the *include callback* is called: +```.cpp +// The callback takes the current path and the wanted include name and returns a template +env.set_include_callback([&env](const std::string& path, const std::string& template_name) { + return env.parse("Hello {{ neighbour }} from " + template_name); +}); // You can disable to search for templates in the file system via env.set_search_included_templates_in_files(false); ``` -Inja will throw an `inja::RenderError` if an included file is not found. To disable this error, you can call `env.set_throw_at_missing_includes(false)`. +Inja will throw an `inja::RenderError` if an included file is not found and no callback is specified. To disable this error, you can call `env.set_throw_at_missing_includes(false)`. #### Assignments Variables can also be defined within the template using the set statment. ```.cpp render("{% set new_hour=23 %}{{ new_hour }}pm", data); // "23pm" +render("{% set time.start=18 %}{{ time.start }}pm", data); // using json pointers ``` +Assignments only set the value within the rendering context; they do not modify the json object passed into the `render` call. + ### Functions A few functions are implemented within the inja template syntax. They can be called with @@ -221,6 +230,10 @@ render("{{ last(guests) }} was last.", data); // "Patir was last." render("{{ sort([3,2,1]) }}", data); // "[1,2,3]" render("{{ sort(guests) }}", data); // "[\"Jeff\", \"Patrick\", \"Tom\"]" +// Join a list with a separator +render("{{ join([1,2,3], \" + \") }}", data); // "1 + 2 + 3" +render("{{ join(guests, \", \") }}", data); // "Jeff, Patrick, Tom" + // Round numbers to a given precision render("{{ round(3.1415, 0) }}", data); // 3 render("{{ round(3.1415, 3) }}", data); // 3.142 @@ -242,6 +255,9 @@ render("{{ float(\"1.8\") > 2 }}", data); // false render("Hello {{ default(neighbour, \"my friend\") }}!", data); // "Hello Peter!" render("Hello {{ default(colleague, \"my friend\") }}!", data); // "Hello my friend!" +// Access an objects value dynamically +render("{{ at(time, \"start\") }} to {{ time.end }}", data); // "16 to 22" + // Check if a key exists in an object render("{{ exists(\"guests\") }}", data); // "true" render("{{ exists(\"city\") }}", data); // "false" @@ -254,25 +270,6 @@ render("{{ isArray(guests) }}", data); // "true" // Implemented type checks: isArray, isBoolean, isFloat, isInteger, isNumber, isObject, isString, ``` -### Whitespace Control - -In the default configuration, no whitespace is removed while rendering the file. To support a more readable template style, you can configure the environment to control whitespaces before and after a statement automatically. While enabling `set_trim_blocks` removes the first newline after a statement, `set_lstrip_blocks` strips tabs and spaces from the beginning of a line to the start of a block. - -```.cpp -Environment env; -env.set_trim_blocks(true); -env.set_lstrip_blocks(true); -``` - -With both `trim_blocks` and `lstrip_blocks` enabled, you can put statements on their own lines. Furthermore, you can also strip whitespaces for both statements and expressions by hand. If you add a minus sign (`-`) to the start or end, the whitespaces before or after that block will be removed: - -```.cpp -render("Hello {{- name -}} !", data); // "Hello Inja!" -render("{% if neighbour in guests -%} I was there{% endif -%} !", data); // Renders without any whitespaces -``` - -Stripping behind a statement or expression also removes any newlines. - ### Callbacks You can create your own and more complex functions with callbacks. These are implemented with `std::function`, so you can for example use C++ lambdas. Inja `Arguments` are a vector of json pointers. @@ -316,6 +313,61 @@ env.add_void_callback("log", 1, [greet](Arguments args) { env.render("{{ log(neighbour) }}", data); // Prints nothing to result, only to cout... ``` +### Template Inheritance + +Template inheritance allows you to build a base *skeleton* template that contains all the common elements and defines blocks that child templates can override. Lets show an example: The base template +```.html + + + + {% block head %} + + {% block title %}{% endblock %} - My Webpage + {% endblock %} + + +
{% block content %}{% endblock %}
+ + +``` +contains three `blocks` that child templates can fill in. The child template +```.html +{% extends "base.html" %} +{% block title %}Index{% endblock %} +{% block head %} + {{ super() }} + +{% endblock %} +{% block content %} +

Index

+

+ Welcome to my blog! +

+{% endblock %} +``` +calls a parent template with the `extends` keyword; it should be the first element in the template. It is possible to render the contents of the parent block by calling `super()`. In the case of multiple levels of `{% extends %}`, super references may be called with an argument (e.g. `super(2)`) to skip levels in the inheritance tree. + +### Whitespace Control + +In the default configuration, no whitespace is removed while rendering the file. To support a more readable template style, you can configure the environment to control whitespaces before and after a statement automatically. While enabling `set_trim_blocks` removes the first newline after a statement, `set_lstrip_blocks` strips tabs and spaces from the beginning of a line to the start of a block. + +```.cpp +Environment env; +env.set_trim_blocks(true); +env.set_lstrip_blocks(true); +``` + +With both `trim_blocks` and `lstrip_blocks` enabled, you can put statements on their own lines. Furthermore, you can also strip whitespaces for both statements and expressions by hand. If you add a minus sign (`-`) to the start or end, the whitespaces before or after that block will be removed: + +```.cpp +render("Hello {{- name -}} !", data); // "Hello Inja!" +render("{% if neighbour in guests -%} I was there{% endif -%} !", data); // Renders without any whitespaces +``` + +Stripping behind a statement or expression also removes any newlines. + ### Comments Comments can be written with the `{# ... #}` syntax. @@ -330,10 +382,10 @@ Inja uses exceptions to handle ill-formed template input. However, exceptions ca ## Supported compilers -Inja uses `string_view` from C++17, but includes the [polyfill](https://github.com/martinmoene/string-view-lite) from martinmoene. This way, the minimum version is C++11. Currently, the following compilers are tested: +Inja uses the `string_view` feature of the C++17 STL. Currently, the following compilers are tested: -- GCC 4.8 - 9 (and possibly later) -- Clang 3.5 - 9 (and possibly later) -- Microsoft Visual C++ 2016 - 2019 (and possibly later) +- GCC 7 - 11 (and possibly later) +- Clang 5 - 12 (and possibly later) +- Microsoft Visual C++ 2017 15.0 - 2022 (and possibly later) -The unit tests fail to compile with GCC 4.8 but should just work fine. A complete list of supported compiler / os versions can be found in the [CI definition](https://github.com/pantor/inja/blob/master/.github/workflows/ci.yml). +A list of supported compiler / os versions can be found in the [CI definition](https://github.com/pantor/inja/blob/master/.github/workflows/ci.yml). diff --git a/doc/Doxyfile b/doc/Doxyfile index 10bbe55..d00832e 100644 --- a/doc/Doxyfile +++ b/doc/Doxyfile @@ -38,7 +38,7 @@ PROJECT_NAME = "Inja" # could be handy for archiving the generated documentation or if some version # control system is used. -PROJECT_NUMBER = 3.1.0 +PROJECT_NUMBER = 3.3.0 # Using the PROJECT_BRIEF tag one can provide an optional one line description # for a project that appears at the top of each page and should give viewer a diff --git a/include/inja/config.hpp b/include/inja/config.hpp index 3f284a4..0a8f9b7 100644 --- a/include/inja/config.hpp +++ b/include/inja/config.hpp @@ -1,12 +1,10 @@ -// Copyright (c) 2019 Pantor. All rights reserved. - #ifndef INCLUDE_INJA_CONFIG_HPP_ #define INCLUDE_INJA_CONFIG_HPP_ #include #include -#include "string_view.hpp" +#include "template.hpp" namespace inja { @@ -25,7 +23,9 @@ struct LexerConfig { std::string expression_close {"}}"}; std::string expression_close_force_rstrip {"-}}"}; std::string comment_open {"{#"}; + std::string comment_open_force_lstrip {"{#-"}; std::string comment_close {"#}"}; + std::string comment_close_force_rstrip {"-#}"}; std::string open_chars {"#{"}; bool trim_blocks {false}; @@ -54,6 +54,9 @@ struct LexerConfig { if (open_chars.find(comment_open[0]) == std::string::npos) { open_chars += comment_open[0]; } + if (open_chars.find(comment_open_force_lstrip[0]) == std::string::npos) { + open_chars += comment_open_force_lstrip[0]; + } } }; @@ -62,6 +65,8 @@ struct LexerConfig { */ struct ParserConfig { bool search_included_templates_in_files {true}; + + std::function include_callback; }; /*! diff --git a/include/inja/environment.hpp b/include/inja/environment.hpp index ed99537..a652935 100644 --- a/include/inja/environment.hpp +++ b/include/inja/environment.hpp @@ -1,5 +1,3 @@ -// Copyright (c) 2019 Pantor. All rights reserved. - #ifndef INCLUDE_INJA_ENVIRONMENT_HPP_ #define INCLUDE_INJA_ENVIRONMENT_HPP_ @@ -8,21 +6,17 @@ #include #include #include - -#include +#include #include "config.hpp" #include "function_storage.hpp" #include "parser.hpp" #include "renderer.hpp" -#include "string_view.hpp" #include "template.hpp" #include "utils.hpp" namespace inja { -using json = nlohmann::json; - /*! * \brief Class for changing the configuration. */ @@ -38,15 +32,14 @@ class Environment { TemplateStorage template_storage; public: - Environment() : Environment("") {} + Environment(): Environment("") {} - explicit Environment(const std::string &global_path) : input_path(global_path), output_path(global_path) {} + explicit Environment(const std::string& global_path): input_path(global_path), output_path(global_path) {} - Environment(const std::string &input_path, const std::string &output_path) - : input_path(input_path), output_path(output_path) {} + Environment(const std::string& input_path, const std::string& output_path): input_path(input_path), output_path(output_path) {} /// Sets the opener and closer for template statements - void set_statement(const std::string &open, const std::string &close) { + void set_statement(const std::string& open, const std::string& close) { lexer_config.statement_open = open; lexer_config.statement_open_no_lstrip = open + "+"; lexer_config.statement_open_force_lstrip = open + "-"; @@ -56,13 +49,13 @@ public: } /// Sets the opener for template line statements - void set_line_statement(const std::string &open) { + void set_line_statement(const std::string& open) { lexer_config.line_statement = open; lexer_config.update_open_chars(); } /// Sets the opener and closer for template expressions - void set_expression(const std::string &open, const std::string &close) { + void set_expression(const std::string& open, const std::string& close) { lexer_config.expression_open = open; lexer_config.expression_open_force_lstrip = open + "-"; lexer_config.expression_close = close; @@ -71,9 +64,11 @@ public: } /// Sets the opener and closer for template comments - void set_comment(const std::string &open, const std::string &close) { + void set_comment(const std::string& open, const std::string& close) { lexer_config.comment_open = open; + lexer_config.comment_open_force_lstrip = open + "-"; lexer_config.comment_close = close; + lexer_config.comment_close_force_rstrip = "-" + close; lexer_config.update_open_chars(); } @@ -97,75 +92,79 @@ public: render_config.throw_at_missing_includes = will_throw; } - Template parse(nonstd::string_view input) { + Template parse(std::string_view input) { Parser parser(parser_config, lexer_config, template_storage, function_storage); return parser.parse(input); } - Template parse_template(const std::string &filename) { + Template parse_template(const std::string& filename) { Parser parser(parser_config, lexer_config, template_storage, function_storage); auto result = Template(parser.load_file(input_path + static_cast(filename))); parser.parse_into_template(result, input_path + static_cast(filename)); return result; } - Template parse_file(const std::string &filename) { + Template parse_file(const std::string& filename) { return parse_template(filename); } - std::string render(nonstd::string_view input, const json &data) { return render(parse(input), data); } + std::string render(std::string_view input, const json& data) { + return render(parse(input), data); + } - std::string render(const Template &tmpl, const json &data) { + std::string render(const Template& tmpl, const json& data) { std::stringstream os; render_to(os, tmpl, data); return os.str(); } - std::string render_file(const std::string &filename, const json &data) { + std::string render_file(const std::string& filename, const json& data) { return render(parse_template(filename), data); } - std::string render_file_with_json_file(const std::string &filename, const std::string &filename_data) { + std::string render_file_with_json_file(const std::string& filename, const std::string& filename_data) { const json data = load_json(filename_data); return render_file(filename, data); } - void write(const std::string &filename, const json &data, const std::string &filename_out) { + void write(const std::string& filename, const json& data, const std::string& filename_out) { std::ofstream file(output_path + filename_out); file << render_file(filename, data); file.close(); } - void write(const Template &temp, const json &data, const std::string &filename_out) { + void write(const Template& temp, const json& data, const std::string& filename_out) { std::ofstream file(output_path + filename_out); file << render(temp, data); file.close(); } - void write_with_json_file(const std::string &filename, const std::string &filename_data, - const std::string &filename_out) { + void write_with_json_file(const std::string& filename, const std::string& filename_data, const std::string& filename_out) { const json data = load_json(filename_data); write(filename, data, filename_out); } - void write_with_json_file(const Template &temp, const std::string &filename_data, const std::string &filename_out) { + void write_with_json_file(const Template& temp, const std::string& filename_data, const std::string& filename_out) { const json data = load_json(filename_data); write(temp, data, filename_out); } - std::ostream &render_to(std::ostream &os, const Template &tmpl, const json &data) { + std::ostream& render_to(std::ostream& os, const Template& tmpl, const json& data) { Renderer(render_config, template_storage, function_storage).render_to(os, tmpl, data); return os; } - std::string load_file(const std::string &filename) { + std::string load_file(const std::string& filename) { Parser parser(parser_config, lexer_config, template_storage, function_storage); return parser.load_file(input_path + filename); } - json load_json(const std::string &filename) { + json load_json(const std::string& filename) { std::ifstream file; - open_file_or_throw(input_path + filename, file); + file.open(input_path + filename); + if (file.fail()) { + INJA_THROW(FileError("failed accessing file at '" + input_path + filename + "'")); + } json j; file >> j; return j; @@ -174,51 +173,61 @@ public: /*! @brief Adds a variadic callback */ - void add_callback(const std::string &name, const CallbackFunction &callback) { + void add_callback(const std::string& name, const CallbackFunction& callback) { add_callback(name, -1, callback); } /*! @brief Adds a variadic void callback */ - void add_void_callback(const std::string &name, const VoidCallbackFunction &callback) { + void add_void_callback(const std::string& name, const VoidCallbackFunction& callback) { add_void_callback(name, -1, callback); } /*! @brief Adds a callback with given number or arguments */ - void add_callback(const std::string &name, int num_args, const CallbackFunction &callback) { + void add_callback(const std::string& name, int num_args, const CallbackFunction& callback) { function_storage.add_callback(name, num_args, callback); } /*! @brief Adds a void callback with given number or arguments */ - void add_void_callback(const std::string &name, int num_args, const VoidCallbackFunction &callback) { - function_storage.add_callback(name, num_args, [callback](Arguments& args) { callback(args); return json(); }); + void add_void_callback(const std::string& name, int num_args, const VoidCallbackFunction& callback) { + function_storage.add_callback(name, num_args, [callback](Arguments& args) { + callback(args); + return json(); + }); } /** Includes a template with a given name into the environment. * Then, a template can be rendered in another template using the * include "" syntax. */ - void include_template(const std::string &name, const Template &tmpl) { + void include_template(const std::string& name, const Template& tmpl) { template_storage[name] = tmpl; } + + /*! + @brief Sets a function that is called when an included file is not found + */ + void set_include_callback(const std::function& callback) { + parser_config.include_callback = callback; + } }; /*! @brief render with default settings to a string */ -inline std::string render(nonstd::string_view input, const json &data) { +inline std::string render(std::string_view input, const json& data) { return Environment().render(input, data); } /*! @brief render with default settings to the given output stream */ -inline void render_to(std::ostream &os, nonstd::string_view input, const json &data) { +inline void render_to(std::ostream& os, std::string_view input, const json& data) { Environment env; env.render_to(os, env.parse(input), data); } diff --git a/include/inja/exceptions.hpp b/include/inja/exceptions.hpp index 2784da8..f0dc8aa 100644 --- a/include/inja/exceptions.hpp +++ b/include/inja/exceptions.hpp @@ -1,5 +1,3 @@ -// Copyright (c) 2020 Pantor. All rights reserved. - #ifndef INCLUDE_INJA_EXCEPTIONS_HPP_ #define INCLUDE_INJA_EXCEPTIONS_HPP_ @@ -19,30 +17,29 @@ struct InjaError : public std::runtime_error { const SourceLocation location; - explicit InjaError(const std::string &type, const std::string &message) + explicit InjaError(const std::string& type, const std::string& message) : std::runtime_error("[inja.exception." + type + "] " + message), type(type), message(message), location({0, 0}) {} - explicit InjaError(const std::string &type, const std::string &message, SourceLocation location) - : std::runtime_error("[inja.exception." + type + "] (at " + std::to_string(location.line) + ":" + - std::to_string(location.column) + ") " + message), + explicit InjaError(const std::string& type, const std::string& message, SourceLocation location) + : std::runtime_error("[inja.exception." + type + "] (at " + std::to_string(location.line) + ":" + std::to_string(location.column) + ") " + message), type(type), message(message), location(location) {} }; struct ParserError : public InjaError { - explicit ParserError(const std::string &message, SourceLocation location) : InjaError("parser_error", message, location) {} + explicit ParserError(const std::string& message, SourceLocation location): InjaError("parser_error", message, location) {} }; struct RenderError : public InjaError { - explicit RenderError(const std::string &message, SourceLocation location) : InjaError("render_error", message, location) {} + explicit RenderError(const std::string& message, SourceLocation location): InjaError("render_error", message, location) {} }; struct FileError : public InjaError { - explicit FileError(const std::string &message) : InjaError("file_error", message) {} - explicit FileError(const std::string &message, SourceLocation location) : InjaError("file_error", message, location) {} + explicit FileError(const std::string& message): InjaError("file_error", message) {} + explicit FileError(const std::string& message, SourceLocation location): InjaError("file_error", message, location) {} }; -struct JsonError : public InjaError { - explicit JsonError(const std::string &message, SourceLocation location) : InjaError("json_error", message, location) {} +struct DataError : public InjaError { + explicit DataError(const std::string& message, SourceLocation location): InjaError("data_error", message, location) {} }; } // namespace inja diff --git a/include/inja/function_storage.hpp b/include/inja/function_storage.hpp index b0091bd..891e45f 100644 --- a/include/inja/function_storage.hpp +++ b/include/inja/function_storage.hpp @@ -1,19 +1,14 @@ -// Copyright (c) 2020 Pantor. All rights reserved. - #ifndef INCLUDE_INJA_FUNCTION_STORAGE_HPP_ #define INCLUDE_INJA_FUNCTION_STORAGE_HPP_ +#include #include -#include "string_view.hpp" - namespace inja { -using json = nlohmann::json; - -using Arguments = std::vector; -using CallbackFunction = std::function; -using VoidCallbackFunction = std::function; +using Arguments = std::vector; +using CallbackFunction = std::function; +using VoidCallbackFunction = std::function; /*! * \brief Class for builtin functions and user-defined callbacks. @@ -64,6 +59,8 @@ public: Round, Sort, Upper, + Super, + Join, Callback, ParenLeft, ParenRight, @@ -71,7 +68,7 @@ public: }; struct FunctionData { - explicit FunctionData(const Operation &op, const CallbackFunction &cb = CallbackFunction{}) : operation(op), callback(cb) {} + explicit FunctionData(const Operation& op, const CallbackFunction& cb = CallbackFunction {}): operation(op), callback(cb) {} const Operation operation; const CallbackFunction callback; }; @@ -80,49 +77,52 @@ private: const int VARIADIC {-1}; std::map, FunctionData> function_storage = { - {std::make_pair("at", 2), FunctionData { Operation::At }}, - {std::make_pair("default", 2), FunctionData { Operation::Default }}, - {std::make_pair("divisibleBy", 2), FunctionData { Operation::DivisibleBy }}, - {std::make_pair("even", 1), FunctionData { Operation::Even }}, - {std::make_pair("exists", 1), FunctionData { Operation::Exists }}, - {std::make_pair("existsIn", 2), FunctionData { Operation::ExistsInObject }}, - {std::make_pair("first", 1), FunctionData { Operation::First }}, - {std::make_pair("float", 1), FunctionData { Operation::Float }}, - {std::make_pair("int", 1), FunctionData { Operation::Int }}, - {std::make_pair("isArray", 1), FunctionData { Operation::IsArray }}, - {std::make_pair("isBoolean", 1), FunctionData { Operation::IsBoolean }}, - {std::make_pair("isFloat", 1), FunctionData { Operation::IsFloat }}, - {std::make_pair("isInteger", 1), FunctionData { Operation::IsInteger }}, - {std::make_pair("isNumber", 1), FunctionData { Operation::IsNumber }}, - {std::make_pair("isObject", 1), FunctionData { Operation::IsObject }}, - {std::make_pair("isString", 1), FunctionData { Operation::IsString }}, - {std::make_pair("last", 1), FunctionData { Operation::Last }}, - {std::make_pair("length", 1), FunctionData { Operation::Length }}, - {std::make_pair("lower", 1), FunctionData { Operation::Lower }}, - {std::make_pair("max", 1), FunctionData { Operation::Max }}, - {std::make_pair("min", 1), FunctionData { Operation::Min }}, - {std::make_pair("odd", 1), FunctionData { Operation::Odd }}, - {std::make_pair("range", 1), FunctionData { Operation::Range }}, - {std::make_pair("round", 2), FunctionData { Operation::Round }}, - {std::make_pair("sort", 1), FunctionData { Operation::Sort }}, - {std::make_pair("upper", 1), FunctionData { Operation::Upper }}, + {std::make_pair("at", 2), FunctionData {Operation::At}}, + {std::make_pair("default", 2), FunctionData {Operation::Default}}, + {std::make_pair("divisibleBy", 2), FunctionData {Operation::DivisibleBy}}, + {std::make_pair("even", 1), FunctionData {Operation::Even}}, + {std::make_pair("exists", 1), FunctionData {Operation::Exists}}, + {std::make_pair("existsIn", 2), FunctionData {Operation::ExistsInObject}}, + {std::make_pair("first", 1), FunctionData {Operation::First}}, + {std::make_pair("float", 1), FunctionData {Operation::Float}}, + {std::make_pair("int", 1), FunctionData {Operation::Int}}, + {std::make_pair("isArray", 1), FunctionData {Operation::IsArray}}, + {std::make_pair("isBoolean", 1), FunctionData {Operation::IsBoolean}}, + {std::make_pair("isFloat", 1), FunctionData {Operation::IsFloat}}, + {std::make_pair("isInteger", 1), FunctionData {Operation::IsInteger}}, + {std::make_pair("isNumber", 1), FunctionData {Operation::IsNumber}}, + {std::make_pair("isObject", 1), FunctionData {Operation::IsObject}}, + {std::make_pair("isString", 1), FunctionData {Operation::IsString}}, + {std::make_pair("last", 1), FunctionData {Operation::Last}}, + {std::make_pair("length", 1), FunctionData {Operation::Length}}, + {std::make_pair("lower", 1), FunctionData {Operation::Lower}}, + {std::make_pair("max", 1), FunctionData {Operation::Max}}, + {std::make_pair("min", 1), FunctionData {Operation::Min}}, + {std::make_pair("odd", 1), FunctionData {Operation::Odd}}, + {std::make_pair("range", 1), FunctionData {Operation::Range}}, + {std::make_pair("round", 2), FunctionData {Operation::Round}}, + {std::make_pair("sort", 1), FunctionData {Operation::Sort}}, + {std::make_pair("upper", 1), FunctionData {Operation::Upper}}, + {std::make_pair("super", 0), FunctionData {Operation::Super}}, + {std::make_pair("super", 1), FunctionData {Operation::Super}}, + {std::make_pair("join", 2), FunctionData {Operation::Join}}, }; public: - void add_builtin(nonstd::string_view name, int num_args, Operation op) { - function_storage.emplace(std::make_pair(static_cast(name), num_args), FunctionData { op }); + void add_builtin(std::string_view name, int num_args, Operation op) { + function_storage.emplace(std::make_pair(static_cast(name), num_args), FunctionData {op}); } - void add_callback(nonstd::string_view name, int num_args, const CallbackFunction &callback) { - function_storage.emplace(std::make_pair(static_cast(name), num_args), FunctionData { Operation::Callback, callback }); + void add_callback(std::string_view name, int num_args, const CallbackFunction& callback) { + function_storage.emplace(std::make_pair(static_cast(name), num_args), FunctionData {Operation::Callback, callback}); } - FunctionData find_function(nonstd::string_view name, int num_args) const { + FunctionData find_function(std::string_view name, int num_args) const { auto it = function_storage.find(std::make_pair(static_cast(name), num_args)); if (it != function_storage.end()) { return it->second; - // Find variadic function + // Find variadic function } else if (num_args > 0) { it = function_storage.find(std::make_pair(static_cast(name), VARIADIC)); if (it != function_storage.end()) { @@ -130,7 +130,7 @@ public: } } - return FunctionData { Operation::None }; + return FunctionData {Operation::None}; } }; diff --git a/include/inja/inja.hpp b/include/inja/inja.hpp index 86b8a0a..381da4b 100644 --- a/include/inja/inja.hpp +++ b/include/inja/inja.hpp @@ -1,22 +1,60 @@ -// Copyright (c) 2020 Pantor. All rights reserved. +/* + ___ _ Version 3.3 + |_ _|_ __ (_) __ _ https://github.com/pantor/inja + | || '_ \ | |/ _` | Licensed under the MIT License . + | || | | || | (_| | + |___|_| |_|/ |\__,_| Copyright (c) 2018-2021 Lars Berscheid + |__/ +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +*/ #ifndef INCLUDE_INJA_INJA_HPP_ #define INCLUDE_INJA_INJA_HPP_ #include +namespace inja { +#ifndef INJA_DATA_TYPE +using json = nlohmann::json; +#else +using json = INJA_DATA_TYPE; +#endif +} // namespace inja + #if (defined(__cpp_exceptions) || defined(__EXCEPTIONS) || defined(_CPPUNWIND)) && !defined(INJA_NOEXCEPTION) - #define INJA_THROW(exception) throw exception +#ifndef INJA_THROW +#define INJA_THROW(exception) throw exception +#endif #else - #include - #define INJA_THROW(exception) std::abort() +#include +#ifndef INJA_THROW +#define INJA_THROW(exception) \ + std::abort(); \ + std::ignore = exception +#endif +#ifndef INJA_NOEXCEPTION +#define INJA_NOEXCEPTION +#endif #endif #include "environment.hpp" #include "exceptions.hpp" #include "parser.hpp" #include "renderer.hpp" -#include "string_view.hpp" #include "template.hpp" #endif // INCLUDE_INJA_INJA_HPP_ diff --git a/include/inja/lexer.hpp b/include/inja/lexer.hpp index e31c3d6..c777600 100644 --- a/include/inja/lexer.hpp +++ b/include/inja/lexer.hpp @@ -1,5 +1,3 @@ -// Copyright (c) 2020 Pantor. All rights reserved. - #ifndef INCLUDE_INJA_LEXER_HPP_ #define INCLUDE_INJA_LEXER_HPP_ @@ -28,6 +26,7 @@ class Lexer { StatementStartForceLstrip, StatementBody, CommentStart, + CommentStartForceLstrip, CommentBody, }; @@ -36,22 +35,21 @@ class Lexer { Number, }; - const LexerConfig &config; + const LexerConfig& config; State state; MinusState minus_state; - nonstd::string_view m_in; + std::string_view m_in; size_t tok_start; size_t pos; - - Token scan_body(nonstd::string_view close, Token::Kind closeKind, nonstd::string_view close_trim = nonstd::string_view(), bool trim = false) { + Token scan_body(std::string_view close, Token::Kind closeKind, std::string_view close_trim = std::string_view(), bool trim = false) { again: // skip whitespace (except for \n as it might be a close) if (tok_start >= m_in.size()) { return make_token(Token::Kind::Eof); } - char ch = m_in[tok_start]; + const char ch = m_in[tok_start]; if (ch == ' ' || ch == '\t' || ch == '\r') { tok_start += 1; goto again; @@ -61,7 +59,7 @@ class Lexer { if (!close_trim.empty() && inja::string_view::starts_with(m_in.substr(tok_start), close_trim)) { state = State::Text; pos = tok_start + close_trim.size(); - Token tok = make_token(closeKind); + const Token tok = make_token(closeKind); skip_whitespaces_and_newlines(); return tok; } @@ -69,7 +67,7 @@ class Lexer { if (inja::string_view::starts_with(m_in.substr(tok_start), close)) { state = State::Text; pos = tok_start + close.size(); - Token tok = make_token(closeKind); + const Token tok = make_token(closeKind); if (trim) { skip_whitespaces_and_first_newline(); } @@ -88,7 +86,7 @@ class Lexer { return scan_id(); } - MinusState current_minus_state = minus_state; + const MinusState current_minus_state = minus_state; if (minus_state == MinusState::Operator) { minus_state = MinusState::Number; } @@ -183,7 +181,7 @@ class Lexer { if (pos >= m_in.size()) { break; } - char ch = m_in[pos]; + const char ch = m_in[pos]; if (!std::isalnum(ch) && ch != '.' && ch != '/' && ch != '_' && ch != '-') { break; } @@ -197,7 +195,7 @@ class Lexer { if (pos >= m_in.size()) { break; } - char ch = m_in[pos]; + const char ch = m_in[pos]; // be very permissive in lexer (we'll catch errors when conversion happens) if (!std::isdigit(ch) && ch != '.' && ch != 'e' && ch != 'E' && ch != '+' && ch != '-') { break; @@ -213,7 +211,7 @@ class Lexer { if (pos >= m_in.size()) { break; } - char ch = m_in[pos++]; + const char ch = m_in[pos++]; if (ch == '\\') { escape = true; } else if (!escape && ch == m_in[tok_start]) { @@ -225,7 +223,9 @@ class Lexer { return make_token(Token::Kind::String); } - Token make_token(Token::Kind kind) const { return Token(kind, string_view::slice(m_in, tok_start, pos)); } + Token make_token(Token::Kind kind) const { + return Token(kind, string_view::slice(m_in, tok_start, pos)); + } void skip_whitespaces_and_newlines() { if (pos < m_in.size()) { @@ -243,7 +243,7 @@ class Lexer { } if (pos < m_in.size()) { - char ch = m_in[pos]; + const char ch = m_in[pos]; if (ch == '\n') { pos += 1; } else if (ch == '\r') { @@ -255,10 +255,10 @@ class Lexer { } } - static nonstd::string_view clear_final_line_if_whitespace(nonstd::string_view text) { - nonstd::string_view result = text; + static std::string_view clear_final_line_if_whitespace(std::string_view text) { + std::string_view result = text; while (!result.empty()) { - char ch = result.back(); + const char ch = result.back(); if (ch == ' ' || ch == '\t') { result.remove_suffix(1); } else if (ch == '\n' || ch == '\r') { @@ -271,13 +271,13 @@ class Lexer { } public: - explicit Lexer(const LexerConfig &config) : config(config), state(State::Text), minus_state(MinusState::Number) {} + explicit Lexer(const LexerConfig& config): config(config), state(State::Text), minus_state(MinusState::Number) {} SourceLocation current_position() const { return get_source_location(m_in, tok_start); } - void start(nonstd::string_view input) { + void start(std::string_view input) { m_in = input; tok_start = 0; pos = 0; @@ -302,8 +302,8 @@ public: default: case State::Text: { // fast-scan to first open character - size_t open_start = m_in.substr(pos).find_first_of(config.open_chars); - if (open_start == nonstd::string_view::npos) { + const size_t open_start = m_in.substr(pos).find_first_of(config.open_chars); + if (open_start == std::string_view::npos) { // didn't find open, return remaining text as text token pos = m_in.size(); return make_token(Token::Kind::Text); @@ -311,7 +311,7 @@ public: pos += open_start; // try to match one of the opening sequences, and get the close - nonstd::string_view open_str = m_in.substr(pos); + std::string_view open_str = m_in.substr(pos); bool must_lstrip = false; if (inja::string_view::starts_with(open_str, config.expression_open)) { if (inja::string_view::starts_with(open_str, config.expression_open_force_lstrip)) { @@ -323,7 +323,7 @@ public: } else if (inja::string_view::starts_with(open_str, config.statement_open)) { if (inja::string_view::starts_with(open_str, config.statement_open_no_lstrip)) { state = State::StatementStartNoLstrip; - } else if (inja::string_view::starts_with(open_str, config.statement_open_force_lstrip )) { + } else if (inja::string_view::starts_with(open_str, config.statement_open_force_lstrip)) { state = State::StatementStartForceLstrip; must_lstrip = true; } else { @@ -331,8 +331,13 @@ public: must_lstrip = config.lstrip_blocks; } } else if (inja::string_view::starts_with(open_str, config.comment_open)) { - state = State::CommentStart; - must_lstrip = config.lstrip_blocks; + if (inja::string_view::starts_with(open_str, config.comment_open_force_lstrip)) { + state = State::CommentStartForceLstrip; + must_lstrip = true; + } else { + state = State::CommentStart; + must_lstrip = config.lstrip_blocks; + } } else if ((pos == 0 || m_in[pos - 1] == '\n') && inja::string_view::starts_with(open_str, config.line_statement)) { state = State::LineStart; } else { @@ -340,7 +345,7 @@ public: goto again; } - nonstd::string_view text = string_view::slice(m_in, tok_start, pos); + std::string_view text = string_view::slice(m_in, tok_start, pos); if (must_lstrip) { text = clear_final_line_if_whitespace(text); } @@ -385,6 +390,11 @@ public: pos += config.comment_open.size(); return make_token(Token::Kind::CommentOpen); } + case State::CommentStartForceLstrip: { + state = State::CommentBody; + pos += config.comment_open_force_lstrip.size(); + return make_token(Token::Kind::CommentOpen); + } case State::ExpressionBody: return scan_body(config.expression_close, Token::Kind::ExpressionClose, config.expression_close_force_rstrip); case State::LineBody: @@ -393,16 +403,21 @@ public: return scan_body(config.statement_close, Token::Kind::StatementClose, config.statement_close_force_rstrip, config.trim_blocks); case State::CommentBody: { // fast-scan to comment close - size_t end = m_in.substr(pos).find(config.comment_close); - if (end == nonstd::string_view::npos) { + const size_t end = m_in.substr(pos).find(config.comment_close); + if (end == std::string_view::npos) { pos = m_in.size(); return make_token(Token::Kind::Eof); } + + // Check for trim pattern + const bool must_rstrip = inja::string_view::starts_with(m_in.substr(pos + end - 1), config.comment_close_force_rstrip); + // return the entire comment in the close token state = State::Text; pos += end + config.comment_close.size(); Token tok = make_token(Token::Kind::CommentClose); - if (config.trim_blocks) { + + if (must_rstrip || config.trim_blocks) { skip_whitespaces_and_first_newline(); } return tok; @@ -410,7 +425,7 @@ public: } } - const LexerConfig &get_config() const { + const LexerConfig& get_config() const { return config; } }; diff --git a/include/inja/node.hpp b/include/inja/node.hpp index 84a8b8a..1946805 100644 --- a/include/inja/node.hpp +++ b/include/inja/node.hpp @@ -1,16 +1,12 @@ -// Copyright (c) 2020 Pantor. All rights reserved. - #ifndef INCLUDE_INJA_NODE_HPP_ #define INCLUDE_INJA_NODE_HPP_ #include +#include #include -#include - #include "function_storage.hpp" -#include "string_view.hpp" - +#include "utils.hpp" namespace inja { @@ -19,7 +15,7 @@ class BlockNode; class TextNode; class ExpressionNode; class LiteralNode; -class JsonNode; +class DataNode; class FunctionNode; class ExpressionListNode; class StatementNode; @@ -28,16 +24,19 @@ class ForArrayStatementNode; class ForObjectStatementNode; class IfStatementNode; class IncludeStatementNode; +class ExtendsStatementNode; +class BlockStatementNode; class SetStatementNode; - class NodeVisitor { public: + virtual ~NodeVisitor() = default; + virtual void visit(const BlockNode& node) = 0; virtual void visit(const TextNode& node) = 0; virtual void visit(const ExpressionNode& node) = 0; virtual void visit(const LiteralNode& node) = 0; - virtual void visit(const JsonNode& node) = 0; + virtual void visit(const DataNode& node) = 0; virtual void visit(const FunctionNode& node) = 0; virtual void visit(const ExpressionListNode& node) = 0; virtual void visit(const StatementNode& node) = 0; @@ -46,6 +45,8 @@ public: virtual void visit(const ForObjectStatementNode& node) = 0; virtual void visit(const IfStatementNode& node) = 0; virtual void visit(const IncludeStatementNode& node) = 0; + virtual void visit(const ExtendsStatementNode& node) = 0; + virtual void visit(const BlockStatementNode& node) = 0; virtual void visit(const SetStatementNode& node) = 0; }; @@ -58,16 +59,15 @@ public: size_t pos; - AstNode(size_t pos) : pos(pos) { } - virtual ~AstNode() { }; + AstNode(size_t pos): pos(pos) {} + virtual ~AstNode() {} }; - class BlockNode : public AstNode { public: std::vector> nodes; - explicit BlockNode() : AstNode(0) {} + explicit BlockNode(): AstNode(0) {} void accept(NodeVisitor& v) const { v.visit(*this); @@ -78,7 +78,7 @@ class TextNode : public AstNode { public: const size_t length; - explicit TextNode(size_t pos, size_t length): AstNode(pos), length(length) { } + explicit TextNode(size_t pos, size_t length): AstNode(pos), length(length) {} void accept(NodeVisitor& v) const { v.visit(*this); @@ -87,7 +87,7 @@ public: class ExpressionNode : public AstNode { public: - explicit ExpressionNode(size_t pos) : AstNode(pos) {} + explicit ExpressionNode(size_t pos): AstNode(pos) {} void accept(NodeVisitor& v) const { v.visit(*this); @@ -96,24 +96,24 @@ public: class LiteralNode : public ExpressionNode { public: - const nlohmann::json value; + const json value; - explicit LiteralNode(const nlohmann::json& value, size_t pos) : ExpressionNode(pos), value(value) { } + explicit LiteralNode(std::string_view data_text, size_t pos): ExpressionNode(pos), value(json::parse(data_text)) {} void accept(NodeVisitor& v) const { v.visit(*this); } }; -class JsonNode : public ExpressionNode { +class DataNode : public ExpressionNode { public: const std::string name; const json::json_pointer ptr; - static std::string convert_dot_to_json_ptr(nonstd::string_view ptr_name) { + static std::string convert_dot_to_ptr(std::string_view ptr_name) { std::string result; do { - nonstd::string_view part; + std::string_view part; std::tie(part, ptr_name) = string_view::split(ptr_name, '.'); result.push_back('/'); result.append(part.begin(), part.end()); @@ -121,7 +121,7 @@ public: return result; } - explicit JsonNode(nonstd::string_view ptr_name, size_t pos) : ExpressionNode(pos), name(ptr_name), ptr(json::json_pointer(convert_dot_to_json_ptr(ptr_name))) { } + explicit DataNode(std::string_view ptr_name, size_t pos): ExpressionNode(pos), name(ptr_name), ptr(json::json_pointer(convert_dot_to_ptr(ptr_name))) {} void accept(NodeVisitor& v) const { v.visit(*this); @@ -144,83 +144,102 @@ public: std::string name; int number_args; // Should also be negative -> -1 for unknown number + std::vector> arguments; CallbackFunction callback; - explicit FunctionNode(nonstd::string_view name, size_t pos) : ExpressionNode(pos), precedence(8), associativity(Associativity::Left), operation(Op::Callback), name(name), number_args(1) { } - explicit FunctionNode(Op operation, size_t pos) : ExpressionNode(pos), operation(operation), number_args(1) { + explicit FunctionNode(std::string_view name, size_t pos) + : ExpressionNode(pos), precedence(8), associativity(Associativity::Left), operation(Op::Callback), name(name), number_args(1) {} + explicit FunctionNode(Op operation, size_t pos): ExpressionNode(pos), operation(operation), number_args(1) { switch (operation) { - case Op::Not: { - precedence = 4; - associativity = Associativity::Left; - } break; - case Op::And: { - precedence = 1; - associativity = Associativity::Left; - } break; - case Op::Or: { - precedence = 1; - associativity = Associativity::Left; - } break; - case Op::In: { - precedence = 2; - associativity = Associativity::Left; - } break; - case Op::Equal: { - precedence = 2; - associativity = Associativity::Left; - } break; - case Op::NotEqual: { - precedence = 2; - associativity = Associativity::Left; - } break; - case Op::Greater: { - precedence = 2; - associativity = Associativity::Left; - } break; - case Op::GreaterEqual: { - precedence = 2; - associativity = Associativity::Left; - } break; - case Op::Less: { - precedence = 2; - associativity = Associativity::Left; - } break; - case Op::LessEqual: { - precedence = 2; - associativity = Associativity::Left; - } break; - case Op::Add: { - precedence = 3; - associativity = Associativity::Left; - } break; - case Op::Subtract: { - precedence = 3; - associativity = Associativity::Left; - } break; - case Op::Multiplication: { - precedence = 4; - associativity = Associativity::Left; - } break; - case Op::Division: { - precedence = 4; - associativity = Associativity::Left; - } break; - case Op::Power: { - precedence = 5; - associativity = Associativity::Right; - } break; - case Op::Modulo: { - precedence = 4; - associativity = Associativity::Left; - } break; - case Op::AtId: { - precedence = 8; - associativity = Associativity::Left; - } break; - default: { - precedence = 1; - associativity = Associativity::Left; - } + case Op::Not: { + number_args = 1; + precedence = 4; + associativity = Associativity::Left; + } break; + case Op::And: { + number_args = 2; + precedence = 1; + associativity = Associativity::Left; + } break; + case Op::Or: { + number_args = 2; + precedence = 1; + associativity = Associativity::Left; + } break; + case Op::In: { + number_args = 2; + precedence = 2; + associativity = Associativity::Left; + } break; + case Op::Equal: { + number_args = 2; + precedence = 2; + associativity = Associativity::Left; + } break; + case Op::NotEqual: { + number_args = 2; + precedence = 2; + associativity = Associativity::Left; + } break; + case Op::Greater: { + number_args = 2; + precedence = 2; + associativity = Associativity::Left; + } break; + case Op::GreaterEqual: { + number_args = 2; + precedence = 2; + associativity = Associativity::Left; + } break; + case Op::Less: { + number_args = 2; + precedence = 2; + associativity = Associativity::Left; + } break; + case Op::LessEqual: { + number_args = 2; + precedence = 2; + associativity = Associativity::Left; + } break; + case Op::Add: { + number_args = 2; + precedence = 3; + associativity = Associativity::Left; + } break; + case Op::Subtract: { + number_args = 2; + precedence = 3; + associativity = Associativity::Left; + } break; + case Op::Multiplication: { + number_args = 2; + precedence = 4; + associativity = Associativity::Left; + } break; + case Op::Division: { + number_args = 2; + precedence = 4; + associativity = Associativity::Left; + } break; + case Op::Power: { + number_args = 2; + precedence = 5; + associativity = Associativity::Right; + } break; + case Op::Modulo: { + number_args = 2; + precedence = 4; + associativity = Associativity::Left; + } break; + case Op::AtId: { + number_args = 2; + precedence = 8; + associativity = Associativity::Left; + } break; + default: { + precedence = 1; + associativity = Associativity::Left; + } } } @@ -231,10 +250,10 @@ public: class ExpressionListNode : public AstNode { public: - std::vector> rpn_output; + std::shared_ptr root; - explicit ExpressionListNode() : AstNode(0) { } - explicit ExpressionListNode(size_t pos) : AstNode(pos) { } + explicit ExpressionListNode(): AstNode(0) {} + explicit ExpressionListNode(size_t pos): AstNode(pos) {} void accept(NodeVisitor& v) const { v.visit(*this); @@ -243,7 +262,7 @@ public: class StatementNode : public AstNode { public: - StatementNode(size_t pos) : AstNode(pos) { } + StatementNode(size_t pos): AstNode(pos) {} virtual void accept(NodeVisitor& v) const = 0; }; @@ -252,9 +271,9 @@ class ForStatementNode : public StatementNode { public: ExpressionListNode condition; BlockNode body; - BlockNode *const parent; + BlockNode* const parent; - ForStatementNode(BlockNode *const parent, size_t pos) : StatementNode(pos), parent(parent) { } + ForStatementNode(BlockNode* const parent, size_t pos): StatementNode(pos), parent(parent) {} virtual void accept(NodeVisitor& v) const = 0; }; @@ -263,7 +282,7 @@ class ForArrayStatementNode : public ForStatementNode { public: const std::string value; - explicit ForArrayStatementNode(const std::string& value, BlockNode *const parent, size_t pos) : ForStatementNode(parent, pos), value(value) { } + explicit ForArrayStatementNode(const std::string& value, BlockNode* const parent, size_t pos): ForStatementNode(parent, pos), value(value) {} void accept(NodeVisitor& v) const { v.visit(*this); @@ -275,7 +294,8 @@ public: const std::string key; const std::string value; - explicit ForObjectStatementNode(const std::string& key, const std::string& value, BlockNode *const parent, size_t pos) : ForStatementNode(parent, pos), key(key), value(value) { } + explicit ForObjectStatementNode(const std::string& key, const std::string& value, BlockNode* const parent, size_t pos) + : ForStatementNode(parent, pos), key(key), value(value) {} void accept(NodeVisitor& v) const { v.visit(*this); @@ -287,13 +307,13 @@ public: ExpressionListNode condition; BlockNode true_statement; BlockNode false_statement; - BlockNode *const parent; + BlockNode* const parent; const bool is_nested; bool has_false_statement {false}; - explicit IfStatementNode(BlockNode *const parent, size_t pos) : StatementNode(pos), parent(parent), is_nested(false) { } - explicit IfStatementNode(bool is_nested, BlockNode *const parent, size_t pos) : StatementNode(pos), parent(parent), is_nested(is_nested) { } + explicit IfStatementNode(BlockNode* const parent, size_t pos): StatementNode(pos), parent(parent), is_nested(false) {} + explicit IfStatementNode(bool is_nested, BlockNode* const parent, size_t pos): StatementNode(pos), parent(parent), is_nested(is_nested) {} void accept(NodeVisitor& v) const { v.visit(*this); @@ -304,7 +324,31 @@ class IncludeStatementNode : public StatementNode { public: const std::string file; - explicit IncludeStatementNode(const std::string& file, size_t pos) : StatementNode(pos), file(file) { } + explicit IncludeStatementNode(const std::string& file, size_t pos): StatementNode(pos), file(file) {} + + void accept(NodeVisitor& v) const { + v.visit(*this); + } +}; + +class ExtendsStatementNode : public StatementNode { +public: + const std::string file; + + explicit ExtendsStatementNode(const std::string& file, size_t pos): StatementNode(pos), file(file) {} + + void accept(NodeVisitor& v) const { + v.visit(*this); + }; +}; + +class BlockStatementNode : public StatementNode { +public: + const std::string name; + BlockNode block; + BlockNode* const parent; + + explicit BlockStatementNode(BlockNode* const parent, const std::string& name, size_t pos): StatementNode(pos), name(name), parent(parent) {} void accept(NodeVisitor& v) const { v.visit(*this); @@ -316,11 +360,11 @@ public: const std::string key; ExpressionListNode expression; - explicit SetStatementNode(const std::string& key, size_t pos) : StatementNode(pos), key(key) { } + explicit SetStatementNode(const std::string& key, size_t pos): StatementNode(pos), key(key) {} void accept(NodeVisitor& v) const { v.visit(*this); - }; + } }; } // namespace inja diff --git a/include/inja/parser.hpp b/include/inja/parser.hpp index 6266c4a..3ea1793 100644 --- a/include/inja/parser.hpp +++ b/include/inja/parser.hpp @@ -1,5 +1,3 @@ -// Copyright (c) 2020 Pantor. All rights reserved. - #ifndef INCLUDE_INJA_PARSER_HPP_ #define INCLUDE_INJA_PARSER_HPP_ @@ -7,7 +5,6 @@ #include #include #include -#include #include #include "config.hpp" @@ -19,19 +16,17 @@ #include "token.hpp" #include "utils.hpp" -#include - namespace inja { /*! * \brief Class for parsing an inja Template. */ class Parser { - const ParserConfig &config; + const ParserConfig& config; Lexer lexer; - TemplateStorage &template_storage; - const FunctionStorage &function_storage; + TemplateStorage& template_storage; + const FunctionStorage& function_storage; Token tok, peek_tok; bool have_peek_tok {false}; @@ -40,21 +35,23 @@ class Parser { size_t current_bracket_level {0}; size_t current_brace_level {0}; - nonstd::string_view json_literal_start; + std::string_view literal_start; - BlockNode *current_block {nullptr}; - ExpressionListNode *current_expression_list {nullptr}; + BlockNode* current_block {nullptr}; + ExpressionListNode* current_expression_list {nullptr}; std::stack> function_stack; + std::vector> arguments; std::stack> operator_stack; std::stack if_statement_stack; std::stack for_statement_stack; + std::stack block_statement_stack; - void throw_parser_error(const std::string &message) { + inline void throw_parser_error(const std::string& message) const { INJA_THROW(ParserError(message, lexer.current_position())); } - void get_next_token() { + inline void get_next_token() { if (have_peek_tok) { tok = peek_tok; have_peek_tok = false; @@ -63,49 +60,108 @@ class Parser { } } - void get_peek_token() { + inline void get_peek_token() { if (!have_peek_tok) { peek_tok = lexer.scan(); have_peek_tok = true; } } - void add_json_literal(const char* content_ptr) { - nonstd::string_view json_text(json_literal_start.data(), tok.text.data() - json_literal_start.data() + tok.text.size()); - current_expression_list->rpn_output.emplace_back(std::make_shared(json::parse(json_text), json_text.data() - content_ptr)); + inline void add_literal(const char* content_ptr) { + std::string_view data_text(literal_start.data(), tok.text.data() - literal_start.data() + tok.text.size()); + arguments.emplace_back(std::make_shared(data_text, data_text.data() - content_ptr)); + } + + inline void add_operator() { + auto function = operator_stack.top(); + operator_stack.pop(); + + for (int i = 0; i < function->number_args; ++i) { + function->arguments.insert(function->arguments.begin(), arguments.back()); + arguments.pop_back(); + } + arguments.emplace_back(function); + } + + void add_to_template_storage(std::string_view path, std::string& template_name) { + if (template_storage.find(template_name) != template_storage.end()) { + return; + } + + std::string original_path = static_cast(path); + std::string original_name = template_name; + + if (config.search_included_templates_in_files) { + // Build the relative path + template_name = original_path + original_name; + if (template_name.compare(0, 2, "./") == 0) { + template_name.erase(0, 2); + } + + if (template_storage.find(template_name) == template_storage.end()) { + // Load file + std::ifstream file; + file.open(template_name); + if (!file.fail()) { + std::string text((std::istreambuf_iterator(file)), std::istreambuf_iterator()); + + auto include_template = Template(text); + template_storage.emplace(template_name, include_template); + parse_into_template(template_storage[template_name], template_name); + return; + } else if (!config.include_callback) { + INJA_THROW(FileError("failed accessing file at '" + template_name + "'")); + } + } + } + + // Try include callback + if (config.include_callback) { + auto include_template = config.include_callback(original_path, original_name); + template_storage.emplace(template_name, include_template); + } + } + + std::string parse_filename(const Token& tok) const { + if (tok.kind != Token::Kind::String) { + throw_parser_error("expected string, got '" + tok.describe() + "'"); + } + + if (tok.text.length() < 2) { + throw_parser_error("expected filename, got '" + static_cast(tok.text) + "'"); + } + + // Remove first and last character "" + return std::string {tok.text.substr(1, tok.text.length() - 2)}; } - bool parse_expression(Template &tmpl, Token::Kind closing) { + bool parse_expression(Template& tmpl, Token::Kind closing) { while (tok.kind != closing && tok.kind != Token::Kind::Eof) { // Literals switch (tok.kind) { case Token::Kind::String: { if (current_brace_level == 0 && current_bracket_level == 0) { - json_literal_start = tok.text; - add_json_literal(tmpl.content.c_str()); + literal_start = tok.text; + add_literal(tmpl.content.c_str()); } - } break; case Token::Kind::Number: { if (current_brace_level == 0 && current_bracket_level == 0) { - json_literal_start = tok.text; - add_json_literal(tmpl.content.c_str()); + literal_start = tok.text; + add_literal(tmpl.content.c_str()); } - } break; case Token::Kind::LeftBracket: { if (current_brace_level == 0 && current_bracket_level == 0) { - json_literal_start = tok.text; + literal_start = tok.text; } current_bracket_level += 1; - } break; case Token::Kind::LeftBrace: { if (current_brace_level == 0 && current_bracket_level == 0) { - json_literal_start = tok.text; + literal_start = tok.text; } current_brace_level += 1; - } break; case Token::Kind::RightBracket: { if (current_bracket_level == 0) { @@ -114,9 +170,8 @@ class Parser { current_bracket_level -= 1; if (current_brace_level == 0 && current_bracket_level == 0) { - add_json_literal(tmpl.content.c_str()); + add_literal(tmpl.content.c_str()); } - } break; case Token::Kind::RightBrace: { if (current_brace_level == 0) { @@ -125,35 +180,35 @@ class Parser { current_brace_level -= 1; if (current_brace_level == 0 && current_bracket_level == 0) { - add_json_literal(tmpl.content.c_str()); + add_literal(tmpl.content.c_str()); } - } break; case Token::Kind::Id: { get_peek_token(); - // Json Literal - if (tok.text == static_cast("true") || tok.text == static_cast("false") || tok.text == static_cast("null")) { + // Data Literal + if (tok.text == static_cast("true") || tok.text == static_cast("false") || + tok.text == static_cast("null")) { if (current_brace_level == 0 && current_bracket_level == 0) { - json_literal_start = tok.text; - add_json_literal(tmpl.content.c_str()); + literal_start = tok.text; + add_literal(tmpl.content.c_str()); } - // Operator + // Operator } else if (tok.text == "and" || tok.text == "or" || tok.text == "in" || tok.text == "not") { goto parse_operator; - // Functions + // Functions } else if (peek_tok.kind == Token::Kind::LeftParen) { operator_stack.emplace(std::make_shared(static_cast(tok.text), tok.text.data() - tmpl.content.c_str())); - function_stack.emplace(operator_stack.top().get(), current_paren_level); + function_stack.emplace(operator_stack.top().get(), current_paren_level); - // Variables + // Variables } else { - current_expression_list->rpn_output.emplace_back(std::make_shared(static_cast(tok.text), tok.text.data() - tmpl.content.c_str())); + arguments.emplace_back(std::make_shared(static_cast(tok.text), tok.text.data() - tmpl.content.c_str())); } - // Operators + // Operators } break; case Token::Kind::Equal: case Token::Kind::NotEqual: @@ -169,7 +224,7 @@ class Parser { case Token::Kind::Percent: case Token::Kind::Dot: { - parse_operator: + parse_operator: FunctionStorage::Operation operation; switch (tok.kind) { case Token::Kind::Id: { @@ -230,13 +285,14 @@ class Parser { } auto function_node = std::make_shared(operation, tok.text.data() - tmpl.content.c_str()); - while (!operator_stack.empty() && ((operator_stack.top()->precedence > function_node->precedence) || (operator_stack.top()->precedence == function_node->precedence && function_node->associativity == FunctionNode::Associativity::Left)) && (operator_stack.top()->operation != FunctionStorage::Operation::ParenLeft)) { - current_expression_list->rpn_output.emplace_back(operator_stack.top()); - operator_stack.pop(); + while (!operator_stack.empty() && + ((operator_stack.top()->precedence > function_node->precedence) || + (operator_stack.top()->precedence == function_node->precedence && function_node->associativity == FunctionNode::Associativity::Left)) && + (operator_stack.top()->operation != FunctionStorage::Operation::ParenLeft)) { + add_operator(); } operator_stack.emplace(function_node); - } break; case Token::Kind::Comma: { if (current_brace_level == 0 && current_bracket_level == 0) { @@ -246,13 +302,11 @@ class Parser { function_stack.top().first->number_args += 1; } - } break; case Token::Kind::Colon: { if (current_brace_level == 0 && current_bracket_level == 0) { throw_parser_error("unexpected ':'"); } - } break; case Token::Kind::LeftParen: { current_paren_level += 1; @@ -264,13 +318,11 @@ class Parser { function_stack.top().first->number_args = 0; } } - } break; case Token::Kind::RightParen: { current_paren_level -= 1; while (!operator_stack.empty() && operator_stack.top()->operation != FunctionStorage::Operation::ParenLeft) { - current_expression_list->rpn_output.emplace_back(operator_stack.top()); - operator_stack.pop(); + add_operator(); } if (!operator_stack.empty() && operator_stack.top()->operation == FunctionStorage::Operation::ParenLeft) { @@ -292,8 +344,7 @@ class Parser { throw_parser_error("internal error at function " + func->name); } - current_expression_list->rpn_output.emplace_back(operator_stack.top()); - operator_stack.pop(); + add_operator(); function_stack.pop(); } } @@ -305,14 +356,20 @@ class Parser { } while (!operator_stack.empty()) { - current_expression_list->rpn_output.emplace_back(operator_stack.top()); - operator_stack.pop(); + add_operator(); + } + + if (arguments.size() == 1) { + current_expression_list->root = arguments[0]; + arguments = {}; + } else if (arguments.size() > 1) { + throw_parser_error("malformed expression"); } return true; } - bool parse_statement(Template &tmpl, Token::Kind closing, nonstd::string_view path) { + bool parse_statement(Template& tmpl, Token::Kind closing, std::string_view path) { if (tok.kind != Token::Kind::Id) { return false; } @@ -329,12 +386,11 @@ class Parser { if (!parse_expression(tmpl, closing)) { return false; } - } else if (tok.text == static_cast("else")) { if (if_statement_stack.empty()) { throw_parser_error("else without matching if"); } - auto &if_statement_data = if_statement_stack.top(); + auto& if_statement_data = if_statement_stack.top(); get_next_token(); if_statement_data->has_false_statement = true; @@ -354,7 +410,6 @@ class Parser { return false; } } - } else if (tok.text == static_cast("endif")) { if (if_statement_stack.empty()) { throw_parser_error("endif without matching if"); @@ -365,12 +420,40 @@ class Parser { if_statement_stack.pop(); } - auto &if_statement_data = if_statement_stack.top(); + auto& if_statement_data = if_statement_stack.top(); get_next_token(); current_block = if_statement_data->parent; if_statement_stack.pop(); + } else if (tok.text == static_cast("block")) { + get_next_token(); + + if (tok.kind != Token::Kind::Id) { + throw_parser_error("expected block name, got '" + tok.describe() + "'"); + } + + const std::string block_name = static_cast(tok.text); + + auto block_statement_node = std::make_shared(current_block, block_name, tok.text.data() - tmpl.content.c_str()); + current_block->nodes.emplace_back(block_statement_node); + block_statement_stack.emplace(block_statement_node.get()); + current_block = &block_statement_node->block; + auto success = tmpl.block_storage.emplace(block_name, block_statement_node); + if (!success.second) { + throw_parser_error("block with the name '" + block_name + "' does already exist"); + } + + get_next_token(); + } else if (tok.text == static_cast("endblock")) { + if (block_statement_stack.empty()) { + throw_parser_error("endblock without matching block"); + } + + auto& block_statement_data = block_statement_stack.top(); + get_next_token(); + current_block = block_statement_data->parent; + block_statement_stack.pop(); } else if (tok.text == static_cast("for")) { get_next_token(); @@ -394,11 +477,13 @@ class Parser { value_token = tok; get_next_token(); - for_statement_node = std::make_shared(static_cast(key_token.text), static_cast(value_token.text), current_block, tok.text.data() - tmpl.content.c_str()); + for_statement_node = std::make_shared(static_cast(key_token.text), static_cast(value_token.text), + current_block, tok.text.data() - tmpl.content.c_str()); - // Array type + // Array type } else { - for_statement_node = std::make_shared(static_cast(value_token.text), current_block, tok.text.data() - tmpl.content.c_str()); + for_statement_node = + std::make_shared(static_cast(value_token.text), current_block, tok.text.data() - tmpl.content.c_str()); } current_block->nodes.emplace_back(for_statement_node); @@ -414,44 +499,34 @@ class Parser { if (!parse_expression(tmpl, closing)) { return false; } - } else if (tok.text == static_cast("endfor")) { if (for_statement_stack.empty()) { throw_parser_error("endfor without matching for"); } - auto &for_statement_data = for_statement_stack.top(); + auto& for_statement_data = for_statement_stack.top(); get_next_token(); current_block = for_statement_data->parent; for_statement_stack.pop(); - } else if (tok.text == static_cast("include")) { get_next_token(); - if (tok.kind != Token::Kind::String) { - throw_parser_error("expected string, got '" + tok.describe() + "'"); - } + std::string template_name = parse_filename(tok); + add_to_template_storage(path, template_name); - // Build the relative path - json json_name = json::parse(tok.text); - std::string pathname = static_cast(path); - pathname += json_name.get_ref(); - if (pathname.compare(0, 2, "./") == 0) { - pathname.erase(0, 2); - } - // sys::path::remove_dots(pathname, true, sys::path::Style::posix); + current_block->nodes.emplace_back(std::make_shared(template_name, tok.text.data() - tmpl.content.c_str())); - if (config.search_included_templates_in_files && template_storage.find(pathname) == template_storage.end()) { - auto include_template = Template(load_file(pathname)); - template_storage.emplace(pathname, include_template); - parse_into_template(template_storage[pathname], pathname); - } + get_next_token(); + } else if (tok.text == static_cast("extends")) { + get_next_token(); - current_block->nodes.emplace_back(std::make_shared(pathname, tok.text.data() - tmpl.content.c_str())); + std::string template_name = parse_filename(tok); + add_to_template_storage(path, template_name); - get_next_token(); + current_block->nodes.emplace_back(std::make_shared(template_name, tok.text.data() - tmpl.content.c_str())); + get_next_token(); } else if (tok.text == static_cast("set")) { get_next_token(); @@ -474,14 +549,13 @@ class Parser { if (!parse_expression(tmpl, closing)) { return false; } - } else { return false; } return true; } - void parse_into(Template &tmpl, nonstd::string_view path) { + void parse_into(Template& tmpl, std::string_view path) { lexer.start(tmpl.content); current_block = &tmpl.root; @@ -495,7 +569,8 @@ class Parser { if (!for_statement_stack.empty()) { throw_parser_error("unmatched for"); } - } return; + } + return; case Token::Kind::Text: { current_block->nodes.emplace_back(std::make_shared(tok.text.data() - tmpl.content.c_str(), tok.text.size())); } break; @@ -545,33 +620,35 @@ class Parser { } } - public: - explicit Parser(const ParserConfig &parser_config, const LexerConfig &lexer_config, - TemplateStorage &template_storage, const FunctionStorage &function_storage) - : config(parser_config), lexer(lexer_config), template_storage(template_storage), function_storage(function_storage) { } + explicit Parser(const ParserConfig& parser_config, const LexerConfig& lexer_config, TemplateStorage& template_storage, + const FunctionStorage& function_storage) + : config(parser_config), lexer(lexer_config), template_storage(template_storage), function_storage(function_storage) {} - Template parse(nonstd::string_view input, nonstd::string_view path) { + Template parse(std::string_view input, std::string_view path) { auto result = Template(static_cast(input)); parse_into(result, path); return result; } - Template parse(nonstd::string_view input) { + Template parse(std::string_view input) { return parse(input, "./"); } - void parse_into_template(Template& tmpl, nonstd::string_view filename) { - nonstd::string_view path = filename.substr(0, filename.find_last_of("/\\") + 1); + void parse_into_template(Template& tmpl, std::string_view filename) { + std::string_view path = filename.substr(0, filename.find_last_of("/\\") + 1); // StringRef path = sys::path::parent_path(filename); auto sub_parser = Parser(config, lexer.get_config(), template_storage, function_storage); sub_parser.parse_into(tmpl, path); } - std::string load_file(nonstd::string_view filename) { + std::string load_file(const std::string& filename) { std::ifstream file; - open_file_or_throw(static_cast(filename), file); + file.open(filename); + if (file.fail()) { + INJA_THROW(FileError("failed accessing file at '" + filename + "'")); + } std::string text((std::istreambuf_iterator(file)), std::istreambuf_iterator()); return text; } diff --git a/include/inja/renderer.hpp b/include/inja/renderer.hpp index 7bc518f..f24bb91 100644 --- a/include/inja/renderer.hpp +++ b/include/inja/renderer.hpp @@ -1,5 +1,3 @@ -// Copyright (c) 2020 Pantor. All rights reserved. - #ifndef INCLUDE_INJA_RENDERER_HPP_ #define INCLUDE_INJA_RENDERER_HPP_ @@ -9,8 +7,6 @@ #include #include -#include - #include "config.hpp" #include "exceptions.hpp" #include "node.hpp" @@ -22,25 +18,31 @@ namespace inja { /*! * \brief Class for rendering a Template with data. */ -class Renderer : public NodeVisitor { +class Renderer : public NodeVisitor { using Op = FunctionStorage::Operation; const RenderConfig config; - const Template *current_template; - const TemplateStorage &template_storage; - const FunctionStorage &function_storage; + const TemplateStorage& template_storage; + const FunctionStorage& function_storage; + + const Template* current_template; + size_t current_level {0}; + std::vector template_stack; + std::vector block_statement_stack; - const json *json_input; - std::ostream *output_stream; + const json* data_input; + std::ostream* output_stream; - json json_additional_data; - json* current_loop_data = &json_additional_data["loop"]; + json additional_data; + json* current_loop_data = &additional_data["loop"]; - std::vector> json_tmp_stack; - std::stack json_eval_stack; - std::stack not_found_stack; + std::vector> data_tmp_stack; + std::stack data_eval_stack; + std::stack not_found_stack; - bool truthy(const json* data) const { + bool break_rendering {false}; + + static bool truthy(const json* data) { if (data->is_boolean()) { return data->get(); } else if (data->is_number()) { @@ -51,7 +53,7 @@ class Renderer : public NodeVisitor { return !data->empty(); } - void print_json(const std::shared_ptr value) { + void print_data(const std::shared_ptr value) { if (value->is_string()) { *output_stream << value->get_ref(); } else if (value->is_number_integer()) { @@ -63,18 +65,20 @@ class Renderer : public NodeVisitor { } const std::shared_ptr eval_expression_list(const ExpressionListNode& expression_list) { - for (auto& expression : expression_list.rpn_output) { - expression->accept(*this); + if (!expression_list.root) { + throw_renderer_error("empty expression", expression_list); } - if (json_eval_stack.empty()) { + expression_list.root->accept(*this); + + if (data_eval_stack.empty()) { throw_renderer_error("empty expression", expression_list); - } else if (json_eval_stack.size() != 1) { + } else if (data_eval_stack.size() != 1) { throw_renderer_error("malformed expression", expression_list); } - auto result = json_eval_stack.top(); - json_eval_stack.pop(); + const auto result = data_eval_stack.top(); + data_eval_stack.pop(); if (!result) { if (not_found_stack.empty()) { @@ -89,51 +93,68 @@ class Renderer : public NodeVisitor { return std::make_shared(*result); } - void throw_renderer_error(const std::string &message, const AstNode& node) { + void throw_renderer_error(const std::string& message, const AstNode& node) { SourceLocation loc = get_source_location(current_template->content, node.pos); INJA_THROW(RenderError(message, loc)); } - template - std::array get_arguments(const AstNode& node) { - if (json_eval_stack.size() < N) { - throw_renderer_error("function needs " + std::to_string(N) + " variables, but has only found " + std::to_string(json_eval_stack.size()), node); + void make_result(const json&& result) { + auto result_ptr = std::make_shared(result); + data_tmp_stack.push_back(result_ptr); + data_eval_stack.push(result_ptr.get()); + } + + template std::array get_arguments(const FunctionNode& node) { + if (node.arguments.size() < N_start + N) { + throw_renderer_error("function needs " + std::to_string(N_start + N) + " variables, but has only found " + std::to_string(node.arguments.size()), node); + } + + for (size_t i = N_start; i < N_start + N; i += 1) { + node.arguments[i]->accept(*this); + } + + if (data_eval_stack.size() < N) { + throw_renderer_error("function needs " + std::to_string(N) + " variables, but has only found " + std::to_string(data_eval_stack.size()), node); } std::array result; for (size_t i = 0; i < N; i += 1) { - result[N - i - 1] = json_eval_stack.top(); - json_eval_stack.pop(); + result[N - i - 1] = data_eval_stack.top(); + data_eval_stack.pop(); if (!result[N - i - 1]) { - auto json_node = not_found_stack.top(); + const auto data_node = not_found_stack.top(); not_found_stack.pop(); if (throw_not_found) { - throw_renderer_error("variable '" + static_cast(json_node->name) + "' not found", *json_node); + throw_renderer_error("variable '" + static_cast(data_node->name) + "' not found", *data_node); } } } return result; } - template - Arguments get_argument_vector(size_t N, const AstNode& node) { - if (json_eval_stack.size() < N) { - throw_renderer_error("function needs " + std::to_string(N) + " variables, but has only found " + std::to_string(json_eval_stack.size()), node); + template Arguments get_argument_vector(const FunctionNode& node) { + const size_t N = node.arguments.size(); + for (auto a : node.arguments) { + a->accept(*this); + } + + if (data_eval_stack.size() < N) { + throw_renderer_error("function needs " + std::to_string(N) + " variables, but has only found " + std::to_string(data_eval_stack.size()), node); } Arguments result {N}; for (size_t i = 0; i < N; i += 1) { - result[N - i - 1] = json_eval_stack.top(); - json_eval_stack.pop(); + result[N - i - 1] = data_eval_stack.top(); + data_eval_stack.pop(); if (!result[N - i - 1]) { - auto json_node = not_found_stack.top(); + const auto data_node = not_found_stack.top(); not_found_stack.pop(); if (throw_not_found) { - throw_renderer_error("variable '" + static_cast(json_node->name) + "' not found", *json_node); + throw_renderer_error("variable '" + static_cast(data_node->name) + "' not found", *data_node); } } } @@ -143,6 +164,10 @@ class Renderer : public NodeVisitor { void visit(const BlockNode& node) { for (auto& n : node.nodes) { n->accept(*this); + + if (break_rendering) { + break; + } } } @@ -150,325 +175,294 @@ class Renderer : public NodeVisitor { output_stream->write(current_template->content.c_str() + node.pos, node.length); } - void visit(const ExpressionNode&) { } + void visit(const ExpressionNode&) {} void visit(const LiteralNode& node) { - json_eval_stack.push(&node.value); + data_eval_stack.push(&node.value); } - void visit(const JsonNode& node) { - if (json_additional_data.contains(node.ptr)) { - json_eval_stack.push(&(json_additional_data[node.ptr])); - - } else if (json_input->contains(node.ptr)) { - json_eval_stack.push(&(*json_input)[node.ptr]); - + void visit(const DataNode& node) { + if (additional_data.contains(node.ptr)) { + data_eval_stack.push(&(additional_data[node.ptr])); + } else if (data_input->contains(node.ptr)) { + data_eval_stack.push(&(*data_input)[node.ptr]); } else { // Try to evaluate as a no-argument callback - auto function_data = function_storage.find_function(node.name, 0); + const auto function_data = function_storage.find_function(node.name, 0); if (function_data.operation == FunctionStorage::Operation::Callback) { Arguments empty_args {}; - auto value = std::make_shared(function_data.callback(empty_args)); - json_tmp_stack.push_back(value); - json_eval_stack.push(value.get()); - + const auto value = std::make_shared(function_data.callback(empty_args)); + data_tmp_stack.push_back(value); + data_eval_stack.push(value.get()); } else { - json_eval_stack.push(nullptr); + data_eval_stack.push(nullptr); not_found_stack.emplace(&node); } } } void visit(const FunctionNode& node) { - std::shared_ptr result_ptr; - switch (node.operation) { case Op::Not: { - auto args = get_arguments<1>(node); - result_ptr = std::make_shared(!truthy(args[0])); - json_tmp_stack.push_back(result_ptr); - json_eval_stack.push(result_ptr.get()); + const auto args = get_arguments<1>(node); + make_result(!truthy(args[0])); } break; case Op::And: { - auto args = get_arguments<2>(node); - result_ptr = std::make_shared(truthy(args[0]) && truthy(args[1])); - json_tmp_stack.push_back(result_ptr); - json_eval_stack.push(result_ptr.get()); + make_result(truthy(get_arguments<1, 0>(node)[0]) && truthy(get_arguments<1, 1>(node)[0])); } break; case Op::Or: { - auto args = get_arguments<2>(node); - result_ptr = std::make_shared(truthy(args[0]) || truthy(args[1])); - json_tmp_stack.push_back(result_ptr); - json_eval_stack.push(result_ptr.get()); + make_result(truthy(get_arguments<1, 0>(node)[0]) || truthy(get_arguments<1, 1>(node)[0])); } break; case Op::In: { - auto args = get_arguments<2>(node); - result_ptr = std::make_shared(std::find(args[1]->begin(), args[1]->end(), *args[0]) != args[1]->end()); - json_tmp_stack.push_back(result_ptr); - json_eval_stack.push(result_ptr.get()); + const auto args = get_arguments<2>(node); + make_result(std::find(args[1]->begin(), args[1]->end(), *args[0]) != args[1]->end()); } break; case Op::Equal: { - auto args = get_arguments<2>(node); - result_ptr = std::make_shared(*args[0] == *args[1]); - json_tmp_stack.push_back(result_ptr); - json_eval_stack.push(result_ptr.get()); + const auto args = get_arguments<2>(node); + make_result(*args[0] == *args[1]); } break; case Op::NotEqual: { - auto args = get_arguments<2>(node); - result_ptr = std::make_shared(*args[0] != *args[1]); - json_tmp_stack.push_back(result_ptr); - json_eval_stack.push(result_ptr.get()); + const auto args = get_arguments<2>(node); + make_result(*args[0] != *args[1]); } break; case Op::Greater: { - auto args = get_arguments<2>(node); - result_ptr = std::make_shared(*args[0] > *args[1]); - json_tmp_stack.push_back(result_ptr); - json_eval_stack.push(result_ptr.get()); + const auto args = get_arguments<2>(node); + make_result(*args[0] > *args[1]); } break; case Op::GreaterEqual: { - auto args = get_arguments<2>(node); - result_ptr = std::make_shared(*args[0] >= *args[1]); - json_tmp_stack.push_back(result_ptr); - json_eval_stack.push(result_ptr.get()); + const auto args = get_arguments<2>(node); + make_result(*args[0] >= *args[1]); } break; case Op::Less: { - auto args = get_arguments<2>(node); - result_ptr = std::make_shared(*args[0] < *args[1]); - json_tmp_stack.push_back(result_ptr); - json_eval_stack.push(result_ptr.get()); + const auto args = get_arguments<2>(node); + make_result(*args[0] < *args[1]); } break; case Op::LessEqual: { - auto args = get_arguments<2>(node); - result_ptr = std::make_shared(*args[0] <= *args[1]); - json_tmp_stack.push_back(result_ptr); - json_eval_stack.push(result_ptr.get()); + const auto args = get_arguments<2>(node); + make_result(*args[0] <= *args[1]); } break; case Op::Add: { - auto args = get_arguments<2>(node); + const auto args = get_arguments<2>(node); if (args[0]->is_string() && args[1]->is_string()) { - result_ptr = std::make_shared(args[0]->get_ref() + args[1]->get_ref()); - json_tmp_stack.push_back(result_ptr); + make_result(args[0]->get_ref() + args[1]->get_ref()); } else if (args[0]->is_number_integer() && args[1]->is_number_integer()) { - result_ptr = std::make_shared(args[0]->get() + args[1]->get()); - json_tmp_stack.push_back(result_ptr); + make_result(args[0]->get() + args[1]->get()); } else { - result_ptr = std::make_shared(args[0]->get() + args[1]->get()); - json_tmp_stack.push_back(result_ptr); + make_result(args[0]->get() + args[1]->get()); } - json_eval_stack.push(result_ptr.get()); } break; case Op::Subtract: { - auto args = get_arguments<2>(node); + const auto args = get_arguments<2>(node); if (args[0]->is_number_integer() && args[1]->is_number_integer()) { - result_ptr = std::make_shared(args[0]->get() - args[1]->get()); - json_tmp_stack.push_back(result_ptr); + make_result(args[0]->get() - args[1]->get()); } else { - result_ptr = std::make_shared(args[0]->get() - args[1]->get()); - json_tmp_stack.push_back(result_ptr); + make_result(args[0]->get() - args[1]->get()); } - json_eval_stack.push(result_ptr.get()); } break; case Op::Multiplication: { - auto args = get_arguments<2>(node); + const auto args = get_arguments<2>(node); if (args[0]->is_number_integer() && args[1]->is_number_integer()) { - result_ptr = std::make_shared(args[0]->get() * args[1]->get()); - json_tmp_stack.push_back(result_ptr); + make_result(args[0]->get() * args[1]->get()); } else { - result_ptr = std::make_shared(args[0]->get() * args[1]->get()); - json_tmp_stack.push_back(result_ptr); + make_result(args[0]->get() * args[1]->get()); } - json_eval_stack.push(result_ptr.get()); } break; case Op::Division: { - auto args = get_arguments<2>(node); + const auto args = get_arguments<2>(node); if (args[1]->get() == 0) { throw_renderer_error("division by zero", node); } - result_ptr = std::make_shared(args[0]->get() / args[1]->get()); - json_tmp_stack.push_back(result_ptr); - json_eval_stack.push(result_ptr.get()); + make_result(args[0]->get() / args[1]->get()); } break; case Op::Power: { - auto args = get_arguments<2>(node); + const auto args = get_arguments<2>(node); if (args[0]->is_number_integer() && args[1]->get() >= 0) { - int result = std::pow(args[0]->get(), args[1]->get()); - result_ptr = std::make_shared(std::move(result)); - json_tmp_stack.push_back(result_ptr); + int result = static_cast(std::pow(args[0]->get(), args[1]->get())); + make_result(result); } else { double result = std::pow(args[0]->get(), args[1]->get()); - result_ptr = std::make_shared(std::move(result)); - json_tmp_stack.push_back(result_ptr); + make_result(result); } - json_eval_stack.push(result_ptr.get()); } break; case Op::Modulo: { - auto args = get_arguments<2>(node); - result_ptr = std::make_shared(args[0]->get() % args[1]->get()); - json_tmp_stack.push_back(result_ptr); - json_eval_stack.push(result_ptr.get()); + const auto args = get_arguments<2>(node); + make_result(args[0]->get() % args[1]->get()); } break; case Op::AtId: { - json_eval_stack.pop(); // Pop id nullptr - auto container = get_arguments<1, false>(node)[0]; + const auto container = get_arguments<1, 0, false>(node)[0]; + node.arguments[1]->accept(*this); if (not_found_stack.empty()) { throw_renderer_error("could not find element with given name", node); } - auto id_node = not_found_stack.top(); + const auto id_node = not_found_stack.top(); not_found_stack.pop(); - json_eval_stack.push(&container->at(id_node->name)); + data_eval_stack.pop(); + data_eval_stack.push(&container->at(id_node->name)); } break; case Op::At: { - auto args = get_arguments<2>(node); - json_eval_stack.push(&args[0]->at(args[1]->get())); + const auto args = get_arguments<2>(node); + if (args[0]->is_object()) { + data_eval_stack.push(&args[0]->at(args[1]->get())); + } else { + data_eval_stack.push(&args[0]->at(args[1]->get())); + } } break; case Op::Default: { - auto default_arg = get_arguments<1>(node)[0]; - auto test_arg = get_arguments<1, false>(node)[0]; - json_eval_stack.push(test_arg ? test_arg : default_arg); + const auto test_arg = get_arguments<1, 0, false>(node)[0]; + data_eval_stack.push(test_arg ? test_arg : get_arguments<1, 1>(node)[0]); } break; case Op::DivisibleBy: { - auto args = get_arguments<2>(node); - int divisor = args[1]->get(); - result_ptr = std::make_shared((divisor != 0) && (args[0]->get() % divisor == 0)); - json_tmp_stack.push_back(result_ptr); - json_eval_stack.push(result_ptr.get()); + const auto args = get_arguments<2>(node); + const int divisor = args[1]->get(); + make_result((divisor != 0) && (args[0]->get() % divisor == 0)); } break; case Op::Even: { - result_ptr = std::make_shared(get_arguments<1>(node)[0]->get() % 2 == 0); - json_tmp_stack.push_back(result_ptr); - json_eval_stack.push(result_ptr.get()); + make_result(get_arguments<1>(node)[0]->get() % 2 == 0); } break; case Op::Exists: { - auto &&name = get_arguments<1>(node)[0]->get_ref(); - result_ptr = std::make_shared(json_input->contains(json::json_pointer(JsonNode::convert_dot_to_json_ptr(name)))); - json_tmp_stack.push_back(result_ptr); - json_eval_stack.push(result_ptr.get()); + auto&& name = get_arguments<1>(node)[0]->get_ref(); + make_result(data_input->contains(json::json_pointer(DataNode::convert_dot_to_ptr(name)))); } break; case Op::ExistsInObject: { - auto args = get_arguments<2>(node); - auto &&name = args[1]->get_ref(); - result_ptr = std::make_shared(args[0]->find(name) != args[0]->end()); - json_tmp_stack.push_back(result_ptr); - json_eval_stack.push(result_ptr.get()); + const auto args = get_arguments<2>(node); + auto&& name = args[1]->get_ref(); + make_result(args[0]->find(name) != args[0]->end()); } break; case Op::First: { - auto result = &get_arguments<1>(node)[0]->front(); - json_eval_stack.push(result); + const auto result = &get_arguments<1>(node)[0]->front(); + data_eval_stack.push(result); } break; case Op::Float: { - result_ptr = std::make_shared(std::stod(get_arguments<1>(node)[0]->get_ref())); - json_tmp_stack.push_back(result_ptr); - json_eval_stack.push(result_ptr.get()); + make_result(std::stod(get_arguments<1>(node)[0]->get_ref())); } break; case Op::Int: { - result_ptr = std::make_shared(std::stoi(get_arguments<1>(node)[0]->get_ref())); - json_tmp_stack.push_back(result_ptr); - json_eval_stack.push(result_ptr.get()); + make_result(std::stoi(get_arguments<1>(node)[0]->get_ref())); } break; case Op::Last: { - auto result = &get_arguments<1>(node)[0]->back(); - json_eval_stack.push(result); + const auto result = &get_arguments<1>(node)[0]->back(); + data_eval_stack.push(result); } break; case Op::Length: { - auto val = get_arguments<1>(node)[0]; + const auto val = get_arguments<1>(node)[0]; if (val->is_string()) { - result_ptr = std::make_shared(val->get_ref().length()); + make_result(val->get_ref().length()); } else { - result_ptr = std::make_shared(val->size()); + make_result(val->size()); } - json_tmp_stack.push_back(result_ptr); - json_eval_stack.push(result_ptr.get()); } break; case Op::Lower: { std::string result = get_arguments<1>(node)[0]->get(); std::transform(result.begin(), result.end(), result.begin(), ::tolower); - result_ptr = std::make_shared(std::move(result)); - json_tmp_stack.push_back(result_ptr); - json_eval_stack.push(result_ptr.get()); + make_result(std::move(result)); } break; case Op::Max: { - auto args = get_arguments<1>(node); - auto result = std::max_element(args[0]->begin(), args[0]->end()); - json_eval_stack.push(&(*result)); + const auto args = get_arguments<1>(node); + const auto result = std::max_element(args[0]->begin(), args[0]->end()); + data_eval_stack.push(&(*result)); } break; case Op::Min: { - auto args = get_arguments<1>(node); - auto result = std::min_element(args[0]->begin(), args[0]->end()); - json_eval_stack.push(&(*result)); + const auto args = get_arguments<1>(node); + const auto result = std::min_element(args[0]->begin(), args[0]->end()); + data_eval_stack.push(&(*result)); } break; case Op::Odd: { - result_ptr = std::make_shared(get_arguments<1>(node)[0]->get() % 2 != 0); - json_tmp_stack.push_back(result_ptr); - json_eval_stack.push(result_ptr.get()); + make_result(get_arguments<1>(node)[0]->get() % 2 != 0); } break; case Op::Range: { std::vector result(get_arguments<1>(node)[0]->get()); std::iota(result.begin(), result.end(), 0); - result_ptr = std::make_shared(std::move(result)); - json_tmp_stack.push_back(result_ptr); - json_eval_stack.push(result_ptr.get()); + make_result(std::move(result)); } break; case Op::Round: { - auto args = get_arguments<2>(node); - int precision = args[1]->get(); - double result = std::round(args[0]->get() * std::pow(10.0, precision)) / std::pow(10.0, precision); - result_ptr = std::make_shared(std::move(result)); - json_tmp_stack.push_back(result_ptr); - json_eval_stack.push(result_ptr.get()); + const auto args = get_arguments<2>(node); + const int precision = args[1]->get(); + const double result = std::round(args[0]->get() * std::pow(10.0, precision)) / std::pow(10.0, precision); + if (precision == 0) { + make_result(int(result)); + } else { + make_result(result); + } } break; case Op::Sort: { - result_ptr = std::make_shared(get_arguments<1>(node)[0]->get>()); + auto result_ptr = std::make_shared(get_arguments<1>(node)[0]->get>()); std::sort(result_ptr->begin(), result_ptr->end()); - json_tmp_stack.push_back(result_ptr); - json_eval_stack.push(result_ptr.get()); + data_tmp_stack.push_back(result_ptr); + data_eval_stack.push(result_ptr.get()); } break; case Op::Upper: { std::string result = get_arguments<1>(node)[0]->get(); std::transform(result.begin(), result.end(), result.begin(), ::toupper); - result_ptr = std::make_shared(std::move(result)); - json_tmp_stack.push_back(result_ptr); - json_eval_stack.push(result_ptr.get()); + make_result(std::move(result)); } break; case Op::IsBoolean: { - result_ptr = std::make_shared(get_arguments<1>(node)[0]->is_boolean()); - json_tmp_stack.push_back(result_ptr); - json_eval_stack.push(result_ptr.get()); + make_result(get_arguments<1>(node)[0]->is_boolean()); } break; case Op::IsNumber: { - result_ptr = std::make_shared(get_arguments<1>(node)[0]->is_number()); - json_tmp_stack.push_back(result_ptr); - json_eval_stack.push(result_ptr.get()); + make_result(get_arguments<1>(node)[0]->is_number()); } break; case Op::IsInteger: { - result_ptr = std::make_shared(get_arguments<1>(node)[0]->is_number_integer()); - json_tmp_stack.push_back(result_ptr); - json_eval_stack.push(result_ptr.get()); + make_result(get_arguments<1>(node)[0]->is_number_integer()); } break; case Op::IsFloat: { - result_ptr = std::make_shared(get_arguments<1>(node)[0]->is_number_float()); - json_tmp_stack.push_back(result_ptr); - json_eval_stack.push(result_ptr.get()); + make_result(get_arguments<1>(node)[0]->is_number_float()); } break; case Op::IsObject: { - result_ptr = std::make_shared(get_arguments<1>(node)[0]->is_object()); - json_tmp_stack.push_back(result_ptr); - json_eval_stack.push(result_ptr.get()); + make_result(get_arguments<1>(node)[0]->is_object()); } break; case Op::IsArray: { - result_ptr = std::make_shared(get_arguments<1>(node)[0]->is_array()); - json_tmp_stack.push_back(result_ptr); - json_eval_stack.push(result_ptr.get()); + make_result(get_arguments<1>(node)[0]->is_array()); } break; case Op::IsString: { - result_ptr = std::make_shared(get_arguments<1>(node)[0]->is_string()); - json_tmp_stack.push_back(result_ptr); - json_eval_stack.push(result_ptr.get()); + make_result(get_arguments<1>(node)[0]->is_string()); } break; case Op::Callback: { - auto args = get_argument_vector(node.number_args, node); - result_ptr = std::make_shared(node.callback(args)); - json_tmp_stack.push_back(result_ptr); - json_eval_stack.push(result_ptr.get()); + auto args = get_argument_vector(node); + make_result(node.callback(args)); + } break; + case Op::Super: { + const auto args = get_argument_vector(node); + const size_t old_level = current_level; + const size_t level_diff = (args.size() == 1) ? args[0]->get() : 1; + const size_t level = current_level + level_diff; + + if (block_statement_stack.empty()) { + throw_renderer_error("super() call is not within a block", node); + } + + if (level < 1 || level > template_stack.size() - 1) { + throw_renderer_error("level of super() call does not match parent templates (between 1 and " + std::to_string(template_stack.size() - 1) + ")", node); + } + + const auto current_block_statement = block_statement_stack.back(); + const Template* new_template = template_stack.at(level); + const Template* old_template = current_template; + const auto block_it = new_template->block_storage.find(current_block_statement->name); + if (block_it != new_template->block_storage.end()) { + current_template = new_template; + current_level = level; + block_it->second->block.accept(*this); + current_level = old_level; + current_template = old_template; + } else { + throw_renderer_error("could not find block with name '" + current_block_statement->name + "'", node); + } + make_result(nullptr); + } break; + case Op::Join: { + const auto args = get_arguments<2>(node); + const auto separator = args[1]->get(); + std::ostringstream os; + std::string sep; + for (const auto& value : *args[0]) { + os << sep; + if (value.is_string()) { + os << value.get(); // otherwise the value is surrounded with "" + } else { + os << value; + } + sep = separator; + } + make_result(os.str()); } break; case Op::ParenLeft: case Op::ParenRight: @@ -478,15 +472,15 @@ class Renderer : public NodeVisitor { } void visit(const ExpressionListNode& node) { - print_json(eval_expression_list(node)); + print_data(eval_expression_list(node)); } - void visit(const StatementNode&) { } + void visit(const StatementNode&) {} - void visit(const ForStatementNode&) { } + void visit(const ForStatementNode&) {} void visit(const ForArrayStatementNode& node) { - auto result = eval_expression_list(node.condition); + const auto result = eval_expression_list(node.condition); if (!result->is_array()) { throw_renderer_error("object must be an array", node); } @@ -500,7 +494,7 @@ class Renderer : public NodeVisitor { (*current_loop_data)["is_first"] = true; (*current_loop_data)["is_last"] = (result->size() <= 1); for (auto it = result->begin(); it != result->end(); ++it) { - json_additional_data[static_cast(node.value)] = *it; + additional_data[static_cast(node.value)] = *it; (*current_loop_data)["index"] = index; (*current_loop_data)["index1"] = index + 1; @@ -515,17 +509,17 @@ class Renderer : public NodeVisitor { ++index; } - json_additional_data[static_cast(node.value)].clear(); + additional_data[static_cast(node.value)].clear(); if (!(*current_loop_data)["parent"].empty()) { - auto tmp = (*current_loop_data)["parent"]; + const auto tmp = (*current_loop_data)["parent"]; *current_loop_data = std::move(tmp); } else { - current_loop_data = &json_additional_data["loop"]; + current_loop_data = &additional_data["loop"]; } } void visit(const ForObjectStatementNode& node) { - auto result = eval_expression_list(node.condition); + const auto result = eval_expression_list(node.condition); if (!result->is_object()) { throw_renderer_error("object must be an object", node); } @@ -538,8 +532,8 @@ class Renderer : public NodeVisitor { (*current_loop_data)["is_first"] = true; (*current_loop_data)["is_last"] = (result->size() <= 1); for (auto it = result->begin(); it != result->end(); ++it) { - json_additional_data[static_cast(node.key)] = it.key(); - json_additional_data[static_cast(node.value)] = it.value(); + additional_data[static_cast(node.key)] = it.key(); + additional_data[static_cast(node.value)] = it.value(); (*current_loop_data)["index"] = index; (*current_loop_data)["index1"] = index + 1; @@ -554,17 +548,17 @@ class Renderer : public NodeVisitor { ++index; } - json_additional_data[static_cast(node.key)].clear(); - json_additional_data[static_cast(node.value)].clear(); + additional_data[static_cast(node.key)].clear(); + additional_data[static_cast(node.value)].clear(); if (!(*current_loop_data)["parent"].empty()) { *current_loop_data = std::move((*current_loop_data)["parent"]); } else { - current_loop_data = &json_additional_data["loop"]; + current_loop_data = &additional_data["loop"]; } } void visit(const IfStatementNode& node) { - auto result = eval_expression_list(node.condition); + const auto result = eval_expression_list(node.condition); if (truthy(result.get())) { node.true_statement.accept(*this); } else if (node.has_false_statement) { @@ -574,35 +568,63 @@ class Renderer : public NodeVisitor { void visit(const IncludeStatementNode& node) { auto sub_renderer = Renderer(config, template_storage, function_storage); - auto included_template_it = template_storage.find(node.file); - + const auto included_template_it = template_storage.find(node.file); if (included_template_it != template_storage.end()) { - sub_renderer.render_to(*output_stream, included_template_it->second, *json_input, &json_additional_data); + sub_renderer.render_to(*output_stream, included_template_it->second, *data_input, &additional_data); } else if (config.throw_at_missing_includes) { throw_renderer_error("include '" + node.file + "' not found", node); } } + void visit(const ExtendsStatementNode& node) { + const auto included_template_it = template_storage.find(node.file); + if (included_template_it != template_storage.end()) { + const Template* parent_template = &included_template_it->second; + render_to(*output_stream, *parent_template, *data_input, &additional_data); + break_rendering = true; + } else if (config.throw_at_missing_includes) { + throw_renderer_error("extends '" + node.file + "' not found", node); + } + } + + void visit(const BlockStatementNode& node) { + const size_t old_level = current_level; + current_level = 0; + current_template = template_stack.front(); + const auto block_it = current_template->block_storage.find(node.name); + if (block_it != current_template->block_storage.end()) { + block_statement_stack.emplace_back(&node); + block_it->second->block.accept(*this); + block_statement_stack.pop_back(); + } + current_level = old_level; + current_template = template_stack.back(); + } + void visit(const SetStatementNode& node) { - json_additional_data[node.key] = *eval_expression_list(node.expression); + std::string ptr = node.key; + replace_substring(ptr, ".", "/"); + ptr = "/" + ptr; + additional_data[json::json_pointer(ptr)] = *eval_expression_list(node.expression); } public: - Renderer(const RenderConfig& config, const TemplateStorage &template_storage, const FunctionStorage &function_storage) - : config(config), template_storage(template_storage), function_storage(function_storage) { } + Renderer(const RenderConfig& config, const TemplateStorage& template_storage, const FunctionStorage& function_storage) + : config(config), template_storage(template_storage), function_storage(function_storage) {} - void render_to(std::ostream &os, const Template &tmpl, const json &data, json *loop_data = nullptr) { + void render_to(std::ostream& os, const Template& tmpl, const json& data, json* loop_data = nullptr) { output_stream = &os; current_template = &tmpl; - json_input = &data; + data_input = &data; if (loop_data) { - json_additional_data = *loop_data; - current_loop_data = &json_additional_data["loop"]; + additional_data = *loop_data; + current_loop_data = &additional_data["loop"]; } + template_stack.emplace_back(current_template); current_template->root.accept(*this); - json_tmp_stack.clear(); + data_tmp_stack.clear(); } }; diff --git a/include/inja/statistics.hpp b/include/inja/statistics.hpp index 71fc719..da33593 100644 --- a/include/inja/statistics.hpp +++ b/include/inja/statistics.hpp @@ -1,11 +1,8 @@ -// Copyright (c) 2019 Pantor. All rights reserved. - #ifndef INCLUDE_INJA_STATISTICS_HPP_ #define INCLUDE_INJA_STATISTICS_HPP_ #include "node.hpp" - namespace inja { /*! @@ -18,24 +15,26 @@ class StatisticsVisitor : public NodeVisitor { } } - void visit(const TextNode&) { } - void visit(const ExpressionNode&) { } - void visit(const LiteralNode&) { } + void visit(const TextNode&) {} + void visit(const ExpressionNode&) {} + void visit(const LiteralNode&) {} - void visit(const JsonNode&) { + void visit(const DataNode&) { variable_counter += 1; } - void visit(const FunctionNode&) { } - - void visit(const ExpressionListNode& node) { - for (auto& n : node.rpn_output) { + void visit(const FunctionNode& node) { + for (auto& n : node.arguments) { n->accept(*this); } } - void visit(const StatementNode&) { } - void visit(const ForStatementNode&) { } + void visit(const ExpressionListNode& node) { + node.root->accept(*this); + } + + void visit(const StatementNode&) {} + void visit(const ForStatementNode&) {} void visit(const ForArrayStatementNode& node) { node.condition.accept(*this); @@ -53,14 +52,20 @@ class StatisticsVisitor : public NodeVisitor { node.false_statement.accept(*this); } - void visit(const IncludeStatementNode&) { } + void visit(const IncludeStatementNode&) {} + + void visit(const ExtendsStatementNode&) {} + + void visit(const BlockStatementNode& node) { + node.block.accept(*this); + } - void visit(const SetStatementNode&) { } + void visit(const SetStatementNode&) {} public: unsigned int variable_counter; - explicit StatisticsVisitor() : variable_counter(0) { } + explicit StatisticsVisitor(): variable_counter(0) {} }; } // namespace inja diff --git a/include/inja/string_view.hpp b/include/inja/string_view.hpp deleted file mode 100644 index 2bb50c9..0000000 --- a/include/inja/string_view.hpp +++ /dev/null @@ -1,1416 +0,0 @@ -// Copyright 2017-2019 by Martin Moene -// -// string-view lite, a C++17-like string_view for C++98 and later. -// For more information see https://github.com/martinmoene/string-view-lite -// -// Distributed under the Boost Software License, Version 1.0. -// (See accompanying file LICENSE.txt or copy at http://www.boost.org/LICENSE_1_0.txt) - -#pragma once - -#ifndef NONSTD_SV_LITE_H_INCLUDED -#define NONSTD_SV_LITE_H_INCLUDED - -#define string_view_lite_MAJOR 1 -#define string_view_lite_MINOR 4 -#define string_view_lite_PATCH 0 - -#define string_view_lite_VERSION \ - nssv_STRINGIFY(string_view_lite_MAJOR) "." nssv_STRINGIFY(string_view_lite_MINOR) "." nssv_STRINGIFY( \ - string_view_lite_PATCH) - -#define nssv_STRINGIFY(x) nssv_STRINGIFY_(x) -#define nssv_STRINGIFY_(x) #x - -// string-view lite configuration: - -#define nssv_STRING_VIEW_DEFAULT 0 -#define nssv_STRING_VIEW_NONSTD 1 -#define nssv_STRING_VIEW_STD 2 - -#if !defined(nssv_CONFIG_SELECT_STRING_VIEW) -#define nssv_CONFIG_SELECT_STRING_VIEW (nssv_HAVE_STD_STRING_VIEW ? nssv_STRING_VIEW_STD : nssv_STRING_VIEW_NONSTD) -#endif - -#if defined(nssv_CONFIG_SELECT_STD_STRING_VIEW) || defined(nssv_CONFIG_SELECT_NONSTD_STRING_VIEW) -#error nssv_CONFIG_SELECT_STD_STRING_VIEW and nssv_CONFIG_SELECT_NONSTD_STRING_VIEW are deprecated and removed, please use nssv_CONFIG_SELECT_STRING_VIEW=nssv_STRING_VIEW_... -#endif - -#ifndef nssv_CONFIG_STD_SV_OPERATOR -#define nssv_CONFIG_STD_SV_OPERATOR 0 -#endif - -#ifndef nssv_CONFIG_USR_SV_OPERATOR -#define nssv_CONFIG_USR_SV_OPERATOR 1 -#endif - -#ifdef nssv_CONFIG_CONVERSION_STD_STRING -#define nssv_CONFIG_CONVERSION_STD_STRING_CLASS_METHODS nssv_CONFIG_CONVERSION_STD_STRING -#define nssv_CONFIG_CONVERSION_STD_STRING_FREE_FUNCTIONS nssv_CONFIG_CONVERSION_STD_STRING -#endif - -#ifndef nssv_CONFIG_CONVERSION_STD_STRING_CLASS_METHODS -#define nssv_CONFIG_CONVERSION_STD_STRING_CLASS_METHODS 1 -#endif - -#ifndef nssv_CONFIG_CONVERSION_STD_STRING_FREE_FUNCTIONS -#define nssv_CONFIG_CONVERSION_STD_STRING_FREE_FUNCTIONS 1 -#endif - -// Control presence of exception handling (try and auto discover): - -#ifndef nssv_CONFIG_NO_EXCEPTIONS -#if defined(__cpp_exceptions) || defined(__EXCEPTIONS) || defined(_CPPUNWIND) -#define nssv_CONFIG_NO_EXCEPTIONS 0 -#else -#define nssv_CONFIG_NO_EXCEPTIONS 1 -#endif -#endif - -// C++ language version detection (C++20 is speculative): -// Note: VC14.0/1900 (VS2015) lacks too much from C++14. - -#ifndef nssv_CPLUSPLUS -#if defined(_MSVC_LANG) && !defined(__clang__) -#define nssv_CPLUSPLUS (_MSC_VER == 1900 ? 201103L : _MSVC_LANG) -#else -#define nssv_CPLUSPLUS __cplusplus -#endif -#endif - -#define nssv_CPP98_OR_GREATER (nssv_CPLUSPLUS >= 199711L) -#define nssv_CPP11_OR_GREATER (nssv_CPLUSPLUS >= 201103L) -#define nssv_CPP11_OR_GREATER_ (nssv_CPLUSPLUS >= 201103L) -#define nssv_CPP14_OR_GREATER (nssv_CPLUSPLUS >= 201402L) -#define nssv_CPP17_OR_GREATER (nssv_CPLUSPLUS >= 201703L) -#define nssv_CPP20_OR_GREATER (nssv_CPLUSPLUS >= 202000L) - -// use C++17 std::string_view if available and requested: - -#if nssv_CPP17_OR_GREATER && defined(__has_include) -#if __has_include( ) -#define nssv_HAVE_STD_STRING_VIEW 1 -#else -#define nssv_HAVE_STD_STRING_VIEW 0 -#endif -#else -#define nssv_HAVE_STD_STRING_VIEW 0 -#endif - -#define nssv_USES_STD_STRING_VIEW \ - ((nssv_CONFIG_SELECT_STRING_VIEW == nssv_STRING_VIEW_STD) || \ - ((nssv_CONFIG_SELECT_STRING_VIEW == nssv_STRING_VIEW_DEFAULT) && nssv_HAVE_STD_STRING_VIEW)) - -#define nssv_HAVE_STARTS_WITH (nssv_CPP20_OR_GREATER || !nssv_USES_STD_STRING_VIEW) -#define nssv_HAVE_ENDS_WITH nssv_HAVE_STARTS_WITH - -// -// Use C++17 std::string_view: -// - -#if nssv_USES_STD_STRING_VIEW - -#include - -// Extensions for std::string: - -#if nssv_CONFIG_CONVERSION_STD_STRING_FREE_FUNCTIONS - -namespace nonstd { - -template > -std::basic_string to_string(std::basic_string_view v, - Allocator const &a = Allocator()) { - return std::basic_string(v.begin(), v.end(), a); -} - -template -std::basic_string_view to_string_view(std::basic_string const &s) { - return std::basic_string_view(s.data(), s.size()); -} - -// Literal operators sv and _sv: - -#if nssv_CONFIG_STD_SV_OPERATOR - -using namespace std::literals::string_view_literals; - -#endif - -#if nssv_CONFIG_USR_SV_OPERATOR - -inline namespace literals { -inline namespace string_view_literals { - -constexpr std::string_view operator"" _sv(const char *str, size_t len) noexcept // (1) -{ - return std::string_view {str, len}; -} - -constexpr std::u16string_view operator"" _sv(const char16_t *str, size_t len) noexcept // (2) -{ - return std::u16string_view {str, len}; -} - -constexpr std::u32string_view operator"" _sv(const char32_t *str, size_t len) noexcept // (3) -{ - return std::u32string_view {str, len}; -} - -constexpr std::wstring_view operator"" _sv(const wchar_t *str, size_t len) noexcept // (4) -{ - return std::wstring_view {str, len}; -} - -} // namespace string_view_literals -} // namespace literals - -#endif // nssv_CONFIG_USR_SV_OPERATOR - -} // namespace nonstd - -#endif // nssv_CONFIG_CONVERSION_STD_STRING_FREE_FUNCTIONS - -namespace nonstd { - -using std::basic_string_view; -using std::string_view; -using std::u16string_view; -using std::u32string_view; -using std::wstring_view; - -// literal "sv" and "_sv", see above - -using std::operator==; -using std::operator!=; -using std::operator<; -using std::operator<=; -using std::operator>; -using std::operator>=; - -using std::operator<<; - -} // namespace nonstd - -#else // nssv_HAVE_STD_STRING_VIEW - -// -// Before C++17: use string_view lite: -// - -// Compiler versions: -// -// MSVC++ 6.0 _MSC_VER == 1200 (Visual Studio 6.0) -// MSVC++ 7.0 _MSC_VER == 1300 (Visual Studio .NET 2002) -// MSVC++ 7.1 _MSC_VER == 1310 (Visual Studio .NET 2003) -// MSVC++ 8.0 _MSC_VER == 1400 (Visual Studio 2005) -// MSVC++ 9.0 _MSC_VER == 1500 (Visual Studio 2008) -// MSVC++ 10.0 _MSC_VER == 1600 (Visual Studio 2010) -// MSVC++ 11.0 _MSC_VER == 1700 (Visual Studio 2012) -// MSVC++ 12.0 _MSC_VER == 1800 (Visual Studio 2013) -// MSVC++ 14.0 _MSC_VER == 1900 (Visual Studio 2015) -// MSVC++ 14.1 _MSC_VER >= 1910 (Visual Studio 2017) - -#if defined(_MSC_VER) && !defined(__clang__) -#define nssv_COMPILER_MSVC_VER (_MSC_VER) -#define nssv_COMPILER_MSVC_VERSION (_MSC_VER / 10 - 10 * (5 + (_MSC_VER < 1900))) -#else -#define nssv_COMPILER_MSVC_VER 0 -#define nssv_COMPILER_MSVC_VERSION 0 -#endif - -#define nssv_COMPILER_VERSION(major, minor, patch) (10 * (10 * (major) + (minor)) + (patch)) - -#if defined(__clang__) -#define nssv_COMPILER_CLANG_VERSION nssv_COMPILER_VERSION(__clang_major__, __clang_minor__, __clang_patchlevel__) -#else -#define nssv_COMPILER_CLANG_VERSION 0 -#endif - -#if defined(__GNUC__) && !defined(__clang__) -#define nssv_COMPILER_GNUC_VERSION nssv_COMPILER_VERSION(__GNUC__, __GNUC_MINOR__, __GNUC_PATCHLEVEL__) -#else -#define nssv_COMPILER_GNUC_VERSION 0 -#endif - -// half-open range [lo..hi): -#define nssv_BETWEEN(v, lo, hi) ((lo) <= (v) && (v) < (hi)) - -// Presence of language and library features: - -#ifdef _HAS_CPP0X -#define nssv_HAS_CPP0X _HAS_CPP0X -#else -#define nssv_HAS_CPP0X 0 -#endif - -// Unless defined otherwise below, consider VC14 as C++11 for variant-lite: - -#if nssv_COMPILER_MSVC_VER >= 1900 -#undef nssv_CPP11_OR_GREATER -#define nssv_CPP11_OR_GREATER 1 -#endif - -#define nssv_CPP11_90 (nssv_CPP11_OR_GREATER_ || nssv_COMPILER_MSVC_VER >= 1500) -#define nssv_CPP11_100 (nssv_CPP11_OR_GREATER_ || nssv_COMPILER_MSVC_VER >= 1600) -#define nssv_CPP11_110 (nssv_CPP11_OR_GREATER_ || nssv_COMPILER_MSVC_VER >= 1700) -#define nssv_CPP11_120 (nssv_CPP11_OR_GREATER_ || nssv_COMPILER_MSVC_VER >= 1800) -#define nssv_CPP11_140 (nssv_CPP11_OR_GREATER_ || nssv_COMPILER_MSVC_VER >= 1900) -#define nssv_CPP11_141 (nssv_CPP11_OR_GREATER_ || nssv_COMPILER_MSVC_VER >= 1910) - -#define nssv_CPP14_000 (nssv_CPP14_OR_GREATER) -#define nssv_CPP17_000 (nssv_CPP17_OR_GREATER) - -// Presence of C++11 language features: - -#define nssv_HAVE_CONSTEXPR_11 nssv_CPP11_140 -#define nssv_HAVE_EXPLICIT_CONVERSION nssv_CPP11_140 -#define nssv_HAVE_INLINE_NAMESPACE nssv_CPP11_140 -#define nssv_HAVE_NOEXCEPT nssv_CPP11_140 -#define nssv_HAVE_NULLPTR nssv_CPP11_100 -#define nssv_HAVE_REF_QUALIFIER nssv_CPP11_140 -#define nssv_HAVE_UNICODE_LITERALS nssv_CPP11_140 -#define nssv_HAVE_USER_DEFINED_LITERALS nssv_CPP11_140 -#define nssv_HAVE_WCHAR16_T nssv_CPP11_100 -#define nssv_HAVE_WCHAR32_T nssv_CPP11_100 - -#if !((nssv_CPP11_OR_GREATER && nssv_COMPILER_CLANG_VERSION) || nssv_BETWEEN(nssv_COMPILER_CLANG_VERSION, 300, 400)) -#define nssv_HAVE_STD_DEFINED_LITERALS nssv_CPP11_140 -#else -#define nssv_HAVE_STD_DEFINED_LITERALS 0 -#endif - -// Presence of C++14 language features: - -#define nssv_HAVE_CONSTEXPR_14 nssv_CPP14_000 - -// Presence of C++17 language features: - -#define nssv_HAVE_NODISCARD nssv_CPP17_000 - -// Presence of C++ library features: - -#define nssv_HAVE_STD_HASH nssv_CPP11_120 - -// C++ feature usage: - -#if nssv_HAVE_CONSTEXPR_11 -#define nssv_constexpr constexpr -#else -#define nssv_constexpr /*constexpr*/ -#endif - -#if nssv_HAVE_CONSTEXPR_14 -#define nssv_constexpr14 constexpr -#else -#define nssv_constexpr14 /*constexpr*/ -#endif - -#if nssv_HAVE_EXPLICIT_CONVERSION -#define nssv_explicit explicit -#else -#define nssv_explicit /*explicit*/ -#endif - -#if nssv_HAVE_INLINE_NAMESPACE -#define nssv_inline_ns inline -#else -#define nssv_inline_ns /*inline*/ -#endif - -#if nssv_HAVE_NOEXCEPT -#define nssv_noexcept noexcept -#else -#define nssv_noexcept /*noexcept*/ -#endif - -//#if nssv_HAVE_REF_QUALIFIER -//# define nssv_ref_qual & -//# define nssv_refref_qual && -//#else -//# define nssv_ref_qual /*&*/ -//# define nssv_refref_qual /*&&*/ -//#endif - -#if nssv_HAVE_NULLPTR -#define nssv_nullptr nullptr -#else -#define nssv_nullptr NULL -#endif - -#if nssv_HAVE_NODISCARD -#define nssv_nodiscard [[nodiscard]] -#else -#define nssv_nodiscard /*[[nodiscard]]*/ -#endif - -// Additional includes: - -#include -#include -#include -#include -#include -#include // std::char_traits<> - -#if !nssv_CONFIG_NO_EXCEPTIONS -#include -#endif - -#if nssv_CPP11_OR_GREATER -#include -#endif - -// Clang, GNUC, MSVC warning suppression macros: - -#if defined(__clang__) -#pragma clang diagnostic ignored "-Wreserved-user-defined-literal" -#pragma clang diagnostic push -#pragma clang diagnostic ignored "-Wuser-defined-literals" -#elif defined(__GNUC__) -#pragma GCC diagnostic push -#pragma GCC diagnostic ignored "-Wliteral-suffix" -#endif // __clang__ - -#if nssv_COMPILER_MSVC_VERSION >= 140 -#define nssv_SUPPRESS_MSGSL_WARNING(expr) [[gsl::suppress(expr)]] -#define nssv_SUPPRESS_MSVC_WARNING(code, descr) __pragma(warning(suppress : code)) -#define nssv_DISABLE_MSVC_WARNINGS(codes) __pragma(warning(push)) __pragma(warning(disable : codes)) -#else -#define nssv_SUPPRESS_MSGSL_WARNING(expr) -#define nssv_SUPPRESS_MSVC_WARNING(code, descr) -#define nssv_DISABLE_MSVC_WARNINGS(codes) -#endif - -#if defined(__clang__) -#define nssv_RESTORE_WARNINGS() _Pragma("clang diagnostic pop") -#elif defined(__GNUC__) -#define nssv_RESTORE_WARNINGS() _Pragma("GCC diagnostic pop") -#elif nssv_COMPILER_MSVC_VERSION >= 140 -#define nssv_RESTORE_WARNINGS() __pragma(warning(pop)) -#else -#define nssv_RESTORE_WARNINGS() -#endif - -// Suppress the following MSVC (GSL) warnings: -// - C4455, non-gsl : 'operator ""sv': literal suffix identifiers that do not -// start with an underscore are reserved -// - C26472, gsl::t.1 : don't use a static_cast for arithmetic conversions; -// use brace initialization, gsl::narrow_cast or gsl::narow -// - C26481: gsl::b.1 : don't use pointer arithmetic. Use span instead - -nssv_DISABLE_MSVC_WARNINGS(4455 26481 26472) - // nssv_DISABLE_CLANG_WARNINGS( "-Wuser-defined-literals" ) - // nssv_DISABLE_GNUC_WARNINGS( -Wliteral-suffix ) - - namespace nonstd { - namespace sv_lite { - -#if nssv_CPP11_OR_GREATER - - namespace detail { - - // Expect tail call optimization to make length() non-recursive: - - template inline constexpr std::size_t length(CharT *s, std::size_t result = 0) { - return *s == '\0' ? result : length(s + 1, result + 1); - } - - } // namespace detail - -#endif // nssv_CPP11_OR_GREATER - - template > class basic_string_view; - - // - // basic_string_view: - // - - template */ - > - class basic_string_view { - public: - // Member types: - - typedef Traits traits_type; - typedef CharT value_type; - - typedef CharT *pointer; - typedef CharT const *const_pointer; - typedef CharT &reference; - typedef CharT const &const_reference; - - typedef const_pointer iterator; - typedef const_pointer const_iterator; - typedef std::reverse_iterator reverse_iterator; - typedef std::reverse_iterator const_reverse_iterator; - - typedef std::size_t size_type; - typedef std::ptrdiff_t difference_type; - - // 24.4.2.1 Construction and assignment: - - nssv_constexpr basic_string_view() nssv_noexcept : data_(nssv_nullptr), size_(0) {} - -#if nssv_CPP11_OR_GREATER - nssv_constexpr basic_string_view(basic_string_view const &other) nssv_noexcept = default; -#else - nssv_constexpr basic_string_view(basic_string_view const &other) nssv_noexcept : data_(other.data_), - size_(other.size_) {} -#endif - - nssv_constexpr basic_string_view(CharT const *s, size_type count) nssv_noexcept // non-standard noexcept - : data_(s), - size_(count) {} - - nssv_constexpr basic_string_view(CharT const *s) nssv_noexcept // non-standard noexcept - : data_(s) -#if nssv_CPP17_OR_GREATER - , - size_(Traits::length(s)) -#elif nssv_CPP11_OR_GREATER - , - size_(detail::length(s)) -#else - , - size_(Traits::length(s)) -#endif - { - } - - // Assignment: - -#if nssv_CPP11_OR_GREATER - nssv_constexpr14 basic_string_view &operator=(basic_string_view const &other) nssv_noexcept = default; -#else - nssv_constexpr14 basic_string_view &operator=(basic_string_view const &other) nssv_noexcept { - data_ = other.data_; - size_ = other.size_; - return *this; - } -#endif - - // 24.4.2.2 Iterator support: - - nssv_constexpr const_iterator begin() const nssv_noexcept { return data_; } - nssv_constexpr const_iterator end() const nssv_noexcept { return data_ + size_; } - - nssv_constexpr const_iterator cbegin() const nssv_noexcept { return begin(); } - nssv_constexpr const_iterator cend() const nssv_noexcept { return end(); } - - nssv_constexpr const_reverse_iterator rbegin() const nssv_noexcept { return const_reverse_iterator(end()); } - nssv_constexpr const_reverse_iterator rend() const nssv_noexcept { return const_reverse_iterator(begin()); } - - nssv_constexpr const_reverse_iterator crbegin() const nssv_noexcept { return rbegin(); } - nssv_constexpr const_reverse_iterator crend() const nssv_noexcept { return rend(); } - - // 24.4.2.3 Capacity: - - nssv_constexpr size_type size() const nssv_noexcept { return size_; } - nssv_constexpr size_type length() const nssv_noexcept { return size_; } - nssv_constexpr size_type max_size() const nssv_noexcept { return (std::numeric_limits::max)(); } - - // since C++20 - nssv_nodiscard nssv_constexpr bool empty() const nssv_noexcept { return 0 == size_; } - - // 24.4.2.4 Element access: - - nssv_constexpr const_reference operator[](size_type pos) const { return data_at(pos); } - - nssv_constexpr14 const_reference at(size_type pos) const { -#if nssv_CONFIG_NO_EXCEPTIONS - assert(pos < size()); -#else - if (pos >= size()) { - throw std::out_of_range("nonstd::string_view::at()"); - } -#endif - return data_at(pos); - } - - nssv_constexpr const_reference front() const { return data_at(0); } - nssv_constexpr const_reference back() const { return data_at(size() - 1); } - - nssv_constexpr const_pointer data() const nssv_noexcept { return data_; } - - // 24.4.2.5 Modifiers: - - nssv_constexpr14 void remove_prefix(size_type n) { - assert(n <= size()); - data_ += n; - size_ -= n; - } - - nssv_constexpr14 void remove_suffix(size_type n) { - assert(n <= size()); - size_ -= n; - } - - nssv_constexpr14 void swap(basic_string_view &other) nssv_noexcept { - using std::swap; - swap(data_, other.data_); - swap(size_, other.size_); - } - - // 24.4.2.6 String operations: - - size_type copy(CharT *dest, size_type n, size_type pos = 0) const { -#if nssv_CONFIG_NO_EXCEPTIONS - assert(pos <= size()); -#else - if (pos > size()) { - throw std::out_of_range("nonstd::string_view::copy()"); - } -#endif - const size_type rlen = (std::min)(n, size() - pos); - - (void)Traits::copy(dest, data() + pos, rlen); - - return rlen; - } - - nssv_constexpr14 basic_string_view substr(size_type pos = 0, size_type n = npos) const { -#if nssv_CONFIG_NO_EXCEPTIONS - assert(pos <= size()); -#else - if (pos > size()) { - throw std::out_of_range("nonstd::string_view::substr()"); - } -#endif - return basic_string_view(data() + pos, (std::min)(n, size() - pos)); - } - - // compare(), 6x: - - nssv_constexpr14 int compare(basic_string_view other) const nssv_noexcept // (1) - { - if (const int result = Traits::compare(data(), other.data(), (std::min)(size(), other.size()))) { - return result; - } - - return size() == other.size() ? 0 : size() < other.size() ? -1 : 1; - } - - nssv_constexpr int compare(size_type pos1, size_type n1, basic_string_view other) const // (2) - { - return substr(pos1, n1).compare(other); - } - - nssv_constexpr int compare(size_type pos1, size_type n1, basic_string_view other, size_type pos2, - size_type n2) const // (3) - { - return substr(pos1, n1).compare(other.substr(pos2, n2)); - } - - nssv_constexpr int compare(CharT const *s) const // (4) - { - return compare(basic_string_view(s)); - } - - nssv_constexpr int compare(size_type pos1, size_type n1, CharT const *s) const // (5) - { - return substr(pos1, n1).compare(basic_string_view(s)); - } - - nssv_constexpr int compare(size_type pos1, size_type n1, CharT const *s, size_type n2) const // (6) - { - return substr(pos1, n1).compare(basic_string_view(s, n2)); - } - - // 24.4.2.7 Searching: - - // starts_with(), 3x, since C++20: - - nssv_constexpr bool starts_with(basic_string_view v) const nssv_noexcept // (1) - { - return size() >= v.size() && compare(0, v.size(), v) == 0; - } - - nssv_constexpr bool starts_with(CharT c) const nssv_noexcept // (2) - { - return starts_with(basic_string_view(&c, 1)); - } - - nssv_constexpr bool starts_with(CharT const *s) const // (3) - { - return starts_with(basic_string_view(s)); - } - - // ends_with(), 3x, since C++20: - - nssv_constexpr bool ends_with(basic_string_view v) const nssv_noexcept // (1) - { - return size() >= v.size() && compare(size() - v.size(), npos, v) == 0; - } - - nssv_constexpr bool ends_with(CharT c) const nssv_noexcept // (2) - { - return ends_with(basic_string_view(&c, 1)); - } - - nssv_constexpr bool ends_with(CharT const *s) const // (3) - { - return ends_with(basic_string_view(s)); - } - - // find(), 4x: - - nssv_constexpr14 size_type find(basic_string_view v, size_type pos = 0) const nssv_noexcept // (1) - { - return assert(v.size() == 0 || v.data() != nssv_nullptr), - pos >= size() ? npos : to_pos(std::search(cbegin() + pos, cend(), v.cbegin(), v.cend(), Traits::eq)); - } - - nssv_constexpr14 size_type find(CharT c, size_type pos = 0) const nssv_noexcept // (2) - { - return find(basic_string_view(&c, 1), pos); - } - - nssv_constexpr14 size_type find(CharT const *s, size_type pos, size_type n) const // (3) - { - return find(basic_string_view(s, n), pos); - } - - nssv_constexpr14 size_type find(CharT const *s, size_type pos = 0) const // (4) - { - return find(basic_string_view(s), pos); - } - - // rfind(), 4x: - - nssv_constexpr14 size_type rfind(basic_string_view v, size_type pos = npos) const nssv_noexcept // (1) - { - if (size() < v.size()) { - return npos; - } - - if (v.empty()) { - return (std::min)(size(), pos); - } - - const_iterator last = cbegin() + (std::min)(size() - v.size(), pos) + v.size(); - const_iterator result = std::find_end(cbegin(), last, v.cbegin(), v.cend(), Traits::eq); - - return result != last ? size_type(result - cbegin()) : npos; - } - - nssv_constexpr14 size_type rfind(CharT c, size_type pos = npos) const nssv_noexcept // (2) - { - return rfind(basic_string_view(&c, 1), pos); - } - - nssv_constexpr14 size_type rfind(CharT const *s, size_type pos, size_type n) const // (3) - { - return rfind(basic_string_view(s, n), pos); - } - - nssv_constexpr14 size_type rfind(CharT const *s, size_type pos = npos) const // (4) - { - return rfind(basic_string_view(s), pos); - } - - // find_first_of(), 4x: - - nssv_constexpr size_type find_first_of(basic_string_view v, size_type pos = 0) const nssv_noexcept // (1) - { - return pos >= size() ? npos - : to_pos(std::find_first_of(cbegin() + pos, cend(), v.cbegin(), v.cend(), Traits::eq)); - } - - nssv_constexpr size_type find_first_of(CharT c, size_type pos = 0) const nssv_noexcept // (2) - { - return find_first_of(basic_string_view(&c, 1), pos); - } - - nssv_constexpr size_type find_first_of(CharT const *s, size_type pos, size_type n) const // (3) - { - return find_first_of(basic_string_view(s, n), pos); - } - - nssv_constexpr size_type find_first_of(CharT const *s, size_type pos = 0) const // (4) - { - return find_first_of(basic_string_view(s), pos); - } - - // find_last_of(), 4x: - - nssv_constexpr size_type find_last_of(basic_string_view v, size_type pos = npos) const nssv_noexcept // (1) - { - return empty() ? npos - : pos >= size() ? find_last_of(v, size() - 1) - : to_pos(std::find_first_of(const_reverse_iterator(cbegin() + pos + 1), crend(), - v.cbegin(), v.cend(), Traits::eq)); - } - - nssv_constexpr size_type find_last_of(CharT c, size_type pos = npos) const nssv_noexcept // (2) - { - return find_last_of(basic_string_view(&c, 1), pos); - } - - nssv_constexpr size_type find_last_of(CharT const *s, size_type pos, size_type count) const // (3) - { - return find_last_of(basic_string_view(s, count), pos); - } - - nssv_constexpr size_type find_last_of(CharT const *s, size_type pos = npos) const // (4) - { - return find_last_of(basic_string_view(s), pos); - } - - // find_first_not_of(), 4x: - - nssv_constexpr size_type find_first_not_of(basic_string_view v, size_type pos = 0) const nssv_noexcept // (1) - { - return pos >= size() ? npos : to_pos(std::find_if(cbegin() + pos, cend(), not_in_view(v))); - } - - nssv_constexpr size_type find_first_not_of(CharT c, size_type pos = 0) const nssv_noexcept // (2) - { - return find_first_not_of(basic_string_view(&c, 1), pos); - } - - nssv_constexpr size_type find_first_not_of(CharT const *s, size_type pos, size_type count) const // (3) - { - return find_first_not_of(basic_string_view(s, count), pos); - } - - nssv_constexpr size_type find_first_not_of(CharT const *s, size_type pos = 0) const // (4) - { - return find_first_not_of(basic_string_view(s), pos); - } - - // find_last_not_of(), 4x: - - nssv_constexpr size_type find_last_not_of(basic_string_view v, size_type pos = npos) const nssv_noexcept // (1) - { - return empty() ? npos - : pos >= size() - ? find_last_not_of(v, size() - 1) - : to_pos(std::find_if(const_reverse_iterator(cbegin() + pos + 1), crend(), not_in_view(v))); - } - - nssv_constexpr size_type find_last_not_of(CharT c, size_type pos = npos) const nssv_noexcept // (2) - { - return find_last_not_of(basic_string_view(&c, 1), pos); - } - - nssv_constexpr size_type find_last_not_of(CharT const *s, size_type pos, size_type count) const // (3) - { - return find_last_not_of(basic_string_view(s, count), pos); - } - - nssv_constexpr size_type find_last_not_of(CharT const *s, size_type pos = npos) const // (4) - { - return find_last_not_of(basic_string_view(s), pos); - } - - // Constants: - -#if nssv_CPP17_OR_GREATER - static nssv_constexpr size_type npos = size_type(-1); -#elif nssv_CPP11_OR_GREATER - enum : size_type { npos = size_type(-1) }; -#else - enum { npos = size_type(-1) }; -#endif - - private: - struct not_in_view { - const basic_string_view v; - - nssv_constexpr explicit not_in_view(basic_string_view v) : v(v) {} - - nssv_constexpr bool operator()(CharT c) const { return npos == v.find_first_of(c); } - }; - - nssv_constexpr size_type to_pos(const_iterator it) const { return it == cend() ? npos : size_type(it - cbegin()); } - - nssv_constexpr size_type to_pos(const_reverse_iterator it) const { - return it == crend() ? npos : size_type(crend() - it - 1); - } - - nssv_constexpr const_reference data_at(size_type pos) const { -#if nssv_BETWEEN(nssv_COMPILER_GNUC_VERSION, 1, 500) - return data_[pos]; -#else - return assert(pos < size()), data_[pos]; -#endif - } - - private: - const_pointer data_; - size_type size_; - - public: -#if nssv_CONFIG_CONVERSION_STD_STRING_CLASS_METHODS - - template - basic_string_view(std::basic_string const &s) nssv_noexcept : data_(s.data()), - size_(s.size()) {} - -#if nssv_HAVE_EXPLICIT_CONVERSION - - template explicit operator std::basic_string() const { - return to_string(Allocator()); - } - -#endif // nssv_HAVE_EXPLICIT_CONVERSION - -#if nssv_CPP11_OR_GREATER - - template > - std::basic_string to_string(Allocator const &a = Allocator()) const { - return std::basic_string(begin(), end(), a); - } - -#else - - std::basic_string to_string() const { return std::basic_string(begin(), end()); } - - template std::basic_string to_string(Allocator const &a) const { - return std::basic_string(begin(), end(), a); - } - -#endif // nssv_CPP11_OR_GREATER - -#endif // nssv_CONFIG_CONVERSION_STD_STRING_CLASS_METHODS - }; - - // - // Non-member functions: - // - - // 24.4.3 Non-member comparison functions: - // lexicographically compare two string views (function template): - - template - nssv_constexpr bool operator==(basic_string_view lhs, - basic_string_view rhs) nssv_noexcept { - return lhs.compare(rhs) == 0; - } - - template - nssv_constexpr bool operator!=(basic_string_view lhs, - basic_string_view rhs) nssv_noexcept { - return lhs.compare(rhs) != 0; - } - - template - nssv_constexpr bool operator<(basic_string_view lhs, - basic_string_view rhs) nssv_noexcept { - return lhs.compare(rhs) < 0; - } - - template - nssv_constexpr bool operator<=(basic_string_view lhs, - basic_string_view rhs) nssv_noexcept { - return lhs.compare(rhs) <= 0; - } - - template - nssv_constexpr bool operator>(basic_string_view lhs, - basic_string_view rhs) nssv_noexcept { - return lhs.compare(rhs) > 0; - } - - template - nssv_constexpr bool operator>=(basic_string_view lhs, - basic_string_view rhs) nssv_noexcept { - return lhs.compare(rhs) >= 0; - } - - // Let S be basic_string_view, and sv be an instance of S. - // Implementations shall provide sufficient additional overloads marked - // constexpr and noexcept so that an object t with an implicit conversion - // to S can be compared according to Table 67. - -#if !nssv_CPP11_OR_GREATER || nssv_BETWEEN(nssv_COMPILER_MSVC_VERSION, 100, 141) - - // accomodate for older compilers: - - // == - - template - nssv_constexpr bool operator==(basic_string_view lhs, char const *rhs) nssv_noexcept { - return lhs.compare(rhs) == 0; - } - - template - nssv_constexpr bool operator==(char const *lhs, basic_string_view rhs) nssv_noexcept { - return rhs.compare(lhs) == 0; - } - - template - nssv_constexpr bool operator==(basic_string_view lhs, - std::basic_string rhs) nssv_noexcept { - return lhs.size() == rhs.size() && lhs.compare(rhs) == 0; - } - - template - nssv_constexpr bool operator==(std::basic_string rhs, - basic_string_view lhs) nssv_noexcept { - return lhs.size() == rhs.size() && lhs.compare(rhs) == 0; - } - - // != - - template - nssv_constexpr bool operator!=(basic_string_view lhs, char const *rhs) nssv_noexcept { - return lhs.compare(rhs) != 0; - } - - template - nssv_constexpr bool operator!=(char const *lhs, basic_string_view rhs) nssv_noexcept { - return rhs.compare(lhs) != 0; - } - - template - nssv_constexpr bool operator!=(basic_string_view lhs, - std::basic_string rhs) nssv_noexcept { - return lhs.size() != rhs.size() && lhs.compare(rhs) != 0; - } - - template - nssv_constexpr bool operator!=(std::basic_string rhs, - basic_string_view lhs) nssv_noexcept { - return lhs.size() != rhs.size() || rhs.compare(lhs) != 0; - } - - // < - - template - nssv_constexpr bool operator<(basic_string_view lhs, char const *rhs) nssv_noexcept { - return lhs.compare(rhs) < 0; - } - - template - nssv_constexpr bool operator<(char const *lhs, basic_string_view rhs) nssv_noexcept { - return rhs.compare(lhs) > 0; - } - - template - nssv_constexpr bool operator<(basic_string_view lhs, - std::basic_string rhs) nssv_noexcept { - return lhs.compare(rhs) < 0; - } - - template - nssv_constexpr bool operator<(std::basic_string rhs, - basic_string_view lhs) nssv_noexcept { - return rhs.compare(lhs) > 0; - } - - // <= - - template - nssv_constexpr bool operator<=(basic_string_view lhs, char const *rhs) nssv_noexcept { - return lhs.compare(rhs) <= 0; - } - - template - nssv_constexpr bool operator<=(char const *lhs, basic_string_view rhs) nssv_noexcept { - return rhs.compare(lhs) >= 0; - } - - template - nssv_constexpr bool operator<=(basic_string_view lhs, - std::basic_string rhs) nssv_noexcept { - return lhs.compare(rhs) <= 0; - } - - template - nssv_constexpr bool operator<=(std::basic_string rhs, - basic_string_view lhs) nssv_noexcept { - return rhs.compare(lhs) >= 0; - } - - // > - - template - nssv_constexpr bool operator>(basic_string_view lhs, char const *rhs) nssv_noexcept { - return lhs.compare(rhs) > 0; - } - - template - nssv_constexpr bool operator>(char const *lhs, basic_string_view rhs) nssv_noexcept { - return rhs.compare(lhs) < 0; - } - - template - nssv_constexpr bool operator>(basic_string_view lhs, - std::basic_string rhs) nssv_noexcept { - return lhs.compare(rhs) > 0; - } - - template - nssv_constexpr bool operator>(std::basic_string rhs, - basic_string_view lhs) nssv_noexcept { - return rhs.compare(lhs) < 0; - } - - // >= - - template - nssv_constexpr bool operator>=(basic_string_view lhs, char const *rhs) nssv_noexcept { - return lhs.compare(rhs) >= 0; - } - - template - nssv_constexpr bool operator>=(char const *lhs, basic_string_view rhs) nssv_noexcept { - return rhs.compare(lhs) <= 0; - } - - template - nssv_constexpr bool operator>=(basic_string_view lhs, - std::basic_string rhs) nssv_noexcept { - return lhs.compare(rhs) >= 0; - } - - template - nssv_constexpr bool operator>=(std::basic_string rhs, - basic_string_view lhs) nssv_noexcept { - return rhs.compare(lhs) <= 0; - } - -#else // newer compilers: - -#define nssv_BASIC_STRING_VIEW_I(T, U) typename std::decay>::type - -#if nssv_BETWEEN(nssv_COMPILER_MSVC_VERSION, 140, 150) -#define nssv_MSVC_ORDER(x) , int = x -#else -#define nssv_MSVC_ORDER(x) /*, int=x*/ -#endif - - // == - - template - nssv_constexpr bool operator==(basic_string_view lhs, - nssv_BASIC_STRING_VIEW_I(CharT, Traits) rhs) nssv_noexcept { - return lhs.compare(rhs) == 0; - } - - template - nssv_constexpr bool operator==(nssv_BASIC_STRING_VIEW_I(CharT, Traits) lhs, - basic_string_view rhs) nssv_noexcept { - return lhs.size() == rhs.size() && lhs.compare(rhs) == 0; - } - - // != - - template - nssv_constexpr bool operator!=(basic_string_view lhs, - nssv_BASIC_STRING_VIEW_I(CharT, Traits) rhs) nssv_noexcept { - return lhs.size() != rhs.size() || lhs.compare(rhs) != 0; - } - - template - nssv_constexpr bool operator!=(nssv_BASIC_STRING_VIEW_I(CharT, Traits) lhs, - basic_string_view rhs) nssv_noexcept { - return lhs.compare(rhs) != 0; - } - - // < - - template - nssv_constexpr bool operator<(basic_string_view lhs, - nssv_BASIC_STRING_VIEW_I(CharT, Traits) rhs) nssv_noexcept { - return lhs.compare(rhs) < 0; - } - - template - nssv_constexpr bool operator<(nssv_BASIC_STRING_VIEW_I(CharT, Traits) lhs, - basic_string_view rhs) nssv_noexcept { - return lhs.compare(rhs) < 0; - } - - // <= - - template - nssv_constexpr bool operator<=(basic_string_view lhs, - nssv_BASIC_STRING_VIEW_I(CharT, Traits) rhs) nssv_noexcept { - return lhs.compare(rhs) <= 0; - } - - template - nssv_constexpr bool operator<=(nssv_BASIC_STRING_VIEW_I(CharT, Traits) lhs, - basic_string_view rhs) nssv_noexcept { - return lhs.compare(rhs) <= 0; - } - - // > - - template - nssv_constexpr bool operator>(basic_string_view lhs, - nssv_BASIC_STRING_VIEW_I(CharT, Traits) rhs) nssv_noexcept { - return lhs.compare(rhs) > 0; - } - - template - nssv_constexpr bool operator>(nssv_BASIC_STRING_VIEW_I(CharT, Traits) lhs, - basic_string_view rhs) nssv_noexcept { - return lhs.compare(rhs) > 0; - } - - // >= - - template - nssv_constexpr bool operator>=(basic_string_view lhs, - nssv_BASIC_STRING_VIEW_I(CharT, Traits) rhs) nssv_noexcept { - return lhs.compare(rhs) >= 0; - } - - template - nssv_constexpr bool operator>=(nssv_BASIC_STRING_VIEW_I(CharT, Traits) lhs, - basic_string_view rhs) nssv_noexcept { - return lhs.compare(rhs) >= 0; - } - -#undef nssv_MSVC_ORDER -#undef nssv_BASIC_STRING_VIEW_I - -#endif // compiler-dependent approach to comparisons - - // 24.4.4 Inserters and extractors: - - namespace detail { - - template void write_padding(Stream &os, std::streamsize n) { - for (std::streamsize i = 0; i < n; ++i) - os.rdbuf()->sputc(os.fill()); - } - - template Stream &write_to_stream(Stream &os, View const &sv) { - typename Stream::sentry sentry(os); - - if (!os) - return os; - - const std::streamsize length = static_cast(sv.length()); - - // Whether, and how, to pad: - const bool pad = (length < os.width()); - const bool left_pad = pad && (os.flags() & std::ios_base::adjustfield) == std::ios_base::right; - - if (left_pad) - write_padding(os, os.width() - length); - - // Write span characters: - os.rdbuf()->sputn(sv.begin(), length); - - if (pad && !left_pad) - write_padding(os, os.width() - length); - - // Reset output stream width: - os.width(0); - - return os; - } - - } // namespace detail - - template - std::basic_ostream &operator<<(std::basic_ostream &os, - basic_string_view sv) { - return detail::write_to_stream(os, sv); - } - - // Several typedefs for common character types are provided: - - typedef basic_string_view string_view; - typedef basic_string_view wstring_view; -#if nssv_HAVE_WCHAR16_T - typedef basic_string_view u16string_view; - typedef basic_string_view u32string_view; -#endif - - } // namespace sv_lite -} // namespace nonstd::sv_lite - -// -// 24.4.6 Suffix for basic_string_view literals: -// - -#if nssv_HAVE_USER_DEFINED_LITERALS - -namespace nonstd { -nssv_inline_ns namespace literals { - nssv_inline_ns namespace string_view_literals { - -#if nssv_CONFIG_STD_SV_OPERATOR && nssv_HAVE_STD_DEFINED_LITERALS - - nssv_constexpr nonstd::sv_lite::string_view operator"" sv(const char *str, size_t len) nssv_noexcept // (1) - { - return nonstd::sv_lite::string_view {str, len}; - } - - nssv_constexpr nonstd::sv_lite::u16string_view operator"" sv(const char16_t *str, size_t len) nssv_noexcept // (2) - { - return nonstd::sv_lite::u16string_view {str, len}; - } - - nssv_constexpr nonstd::sv_lite::u32string_view operator"" sv(const char32_t *str, size_t len) nssv_noexcept // (3) - { - return nonstd::sv_lite::u32string_view {str, len}; - } - - nssv_constexpr nonstd::sv_lite::wstring_view operator"" sv(const wchar_t *str, size_t len) nssv_noexcept // (4) - { - return nonstd::sv_lite::wstring_view {str, len}; - } - -#endif // nssv_CONFIG_STD_SV_OPERATOR && nssv_HAVE_STD_DEFINED_LITERALS - -#if nssv_CONFIG_USR_SV_OPERATOR - - nssv_constexpr nonstd::sv_lite::string_view operator"" _sv(const char *str, size_t len) nssv_noexcept // (1) - { - return nonstd::sv_lite::string_view {str, len}; - } - - nssv_constexpr nonstd::sv_lite::u16string_view operator"" _sv(const char16_t *str, size_t len) nssv_noexcept // (2) - { - return nonstd::sv_lite::u16string_view {str, len}; - } - - nssv_constexpr nonstd::sv_lite::u32string_view operator"" _sv(const char32_t *str, size_t len) nssv_noexcept // (3) - { - return nonstd::sv_lite::u32string_view {str, len}; - } - - nssv_constexpr nonstd::sv_lite::wstring_view operator"" _sv(const wchar_t *str, size_t len) nssv_noexcept // (4) - { - return nonstd::sv_lite::wstring_view {str, len}; - } - -#endif // nssv_CONFIG_USR_SV_OPERATOR - } -} -} // namespace nonstd - -#endif - -// -// Extensions for std::string: -// - -#if nssv_CONFIG_CONVERSION_STD_STRING_FREE_FUNCTIONS - -namespace nonstd { -namespace sv_lite { - -// Exclude MSVC 14 (19.00): it yields ambiguous to_string(): - -#if nssv_CPP11_OR_GREATER && nssv_COMPILER_MSVC_VERSION != 140 - -template > -std::basic_string to_string(basic_string_view v, - Allocator const &a = Allocator()) { - return std::basic_string(v.begin(), v.end(), a); -} - -#else - -template std::basic_string to_string(basic_string_view v) { - return std::basic_string(v.begin(), v.end()); -} - -template -std::basic_string to_string(basic_string_view v, Allocator const &a) { - return std::basic_string(v.begin(), v.end(), a); -} - -#endif // nssv_CPP11_OR_GREATER - -template -basic_string_view to_string_view(std::basic_string const &s) { - return basic_string_view(s.data(), s.size()); -} - -} // namespace sv_lite -} // namespace nonstd - -#endif // nssv_CONFIG_CONVERSION_STD_STRING_FREE_FUNCTIONS - -// -// make types and algorithms available in namespace nonstd: -// - -namespace nonstd { - -using sv_lite::basic_string_view; -using sv_lite::string_view; -using sv_lite::wstring_view; - -#if nssv_HAVE_WCHAR16_T -using sv_lite::u16string_view; -#endif -#if nssv_HAVE_WCHAR32_T -using sv_lite::u32string_view; -#endif - -// literal "sv" - -using sv_lite::operator==; -using sv_lite::operator!=; -using sv_lite::operator<; -using sv_lite::operator<=; -using sv_lite::operator>; -using sv_lite::operator>=; - -using sv_lite::operator<<; - -#if nssv_CONFIG_CONVERSION_STD_STRING_FREE_FUNCTIONS -using sv_lite::to_string; -using sv_lite::to_string_view; -#endif - -} // namespace nonstd - -// 24.4.5 Hash support (C++11): - -// Note: The hash value of a string view object is equal to the hash value of -// the corresponding string object. - -#if nssv_HAVE_STD_HASH - -#include - -namespace std { - -template <> struct hash { -public: - std::size_t operator()(nonstd::string_view v) const nssv_noexcept { - return std::hash()(std::string(v.data(), v.size())); - } -}; - -template <> struct hash { -public: - std::size_t operator()(nonstd::wstring_view v) const nssv_noexcept { - return std::hash()(std::wstring(v.data(), v.size())); - } -}; - -template <> struct hash { -public: - std::size_t operator()(nonstd::u16string_view v) const nssv_noexcept { - return std::hash()(std::u16string(v.data(), v.size())); - } -}; - -template <> struct hash { -public: - std::size_t operator()(nonstd::u32string_view v) const nssv_noexcept { - return std::hash()(std::u32string(v.data(), v.size())); - } -}; - -} // namespace std - -#endif // nssv_HAVE_STD_HASH - -nssv_RESTORE_WARNINGS() - -#endif // nssv_HAVE_STD_STRING_VIEW -#endif // NONSTD_SV_LITE_H_INCLUDED diff --git a/include/inja/template.hpp b/include/inja/template.hpp index 9de0a96..a396dd5 100644 --- a/include/inja/template.hpp +++ b/include/inja/template.hpp @@ -1,5 +1,3 @@ -// Copyright (c) 2019 Pantor. All rights reserved. - #ifndef INCLUDE_INJA_TEMPLATE_HPP_ #define INCLUDE_INJA_TEMPLATE_HPP_ @@ -11,7 +9,6 @@ #include "node.hpp" #include "statistics.hpp" - namespace inja { /*! @@ -20,9 +17,10 @@ namespace inja { struct Template { BlockNode root; std::string content; + std::map> block_storage; - explicit Template() { } - explicit Template(const std::string& content): content(content) { } + explicit Template() {} + explicit Template(const std::string& content): content(content) {} /// Return number of variables (total number, not distinct ones) in the template int count_variables() { diff --git a/include/inja/token.hpp b/include/inja/token.hpp index c000138..207e78e 100644 --- a/include/inja/token.hpp +++ b/include/inja/token.hpp @@ -1,11 +1,8 @@ -// Copyright (c) 2020 Pantor. All rights reserved. - #ifndef INCLUDE_INJA_TOKEN_HPP_ #define INCLUDE_INJA_TOKEN_HPP_ #include - -#include "string_view.hpp" +#include namespace inja { @@ -50,12 +47,12 @@ struct Token { Unknown, Eof, }; - + Kind kind {Kind::Unknown}; - nonstd::string_view text; + std::string_view text; explicit constexpr Token() = default; - explicit constexpr Token(Kind kind, nonstd::string_view text) : kind(kind), text(text) {} + explicit constexpr Token(Kind kind, std::string_view text): kind(kind), text(text) {} std::string describe() const { switch (kind) { diff --git a/include/inja/utils.hpp b/include/inja/utils.hpp index fb1736c..4de05fd 100644 --- a/include/inja/utils.hpp +++ b/include/inja/utils.hpp @@ -1,53 +1,42 @@ -// Copyright (c) 2020 Pantor. All rights reserved. - #ifndef INCLUDE_INJA_UTILS_HPP_ #define INCLUDE_INJA_UTILS_HPP_ #include #include #include +#include #include #include "exceptions.hpp" -#include "string_view.hpp" namespace inja { -inline void open_file_or_throw(const std::string &path, std::ifstream &file) { - file.exceptions(std::ifstream::failbit | std::ifstream::badbit); - try { - file.open(path); - } catch (const std::ios_base::failure & /*e*/) { - INJA_THROW(FileError("failed accessing file at '" + path + "'")); - } -} - namespace string_view { -inline nonstd::string_view slice(nonstd::string_view view, size_t start, size_t end) { +inline std::string_view slice(std::string_view view, size_t start, size_t end) { start = std::min(start, view.size()); end = std::min(std::max(start, end), view.size()); return view.substr(start, end - start); } -inline std::pair split(nonstd::string_view view, char Separator) { +inline std::pair split(std::string_view view, char Separator) { size_t idx = view.find(Separator); - if (idx == nonstd::string_view::npos) { - return std::make_pair(view, nonstd::string_view()); + if (idx == std::string_view::npos) { + return std::make_pair(view, std::string_view()); } - return std::make_pair(slice(view, 0, idx), slice(view, idx + 1, nonstd::string_view::npos)); + return std::make_pair(slice(view, 0, idx), slice(view, idx + 1, std::string_view::npos)); } -inline bool starts_with(nonstd::string_view view, nonstd::string_view prefix) { +inline bool starts_with(std::string_view view, std::string_view prefix) { return (view.size() >= prefix.size() && view.compare(0, prefix.size(), prefix) == 0); } } // namespace string_view -inline SourceLocation get_source_location(nonstd::string_view content, size_t pos) { +inline SourceLocation get_source_location(std::string_view content, size_t pos) { // Get line and offset position (starts at 1:1) auto sliced = string_view::slice(content, 0, pos); std::size_t last_newline = sliced.rfind("\n"); - if (last_newline == nonstd::string_view::npos) { + if (last_newline == std::string_view::npos) { return {1, sliced.length() + 1}; } @@ -65,6 +54,17 @@ inline SourceLocation get_source_location(nonstd::string_view content, size_t po return {count_lines + 1, sliced.length() - last_newline}; } +inline void replace_substring(std::string& s, const std::string& f, const std::string& t) { + if (f.empty()) { + return; + } + for (auto pos = s.find(f); // find first occurrence of f + pos != std::string::npos; // make sure f was found + s.replace(pos, f.size(), t), // replace with t, and + pos = s.find(f, pos + t.size())) // find next occurrence of f + {} +} + } // namespace inja #endif // INCLUDE_INJA_UTILS_HPP_ diff --git a/single_include/inja/inja.hpp b/single_include/inja/inja.hpp index 42dea1c..6aad870 100644 --- a/single_include/inja/inja.hpp +++ b/single_include/inja/inja.hpp @@ -1,20 +1,57 @@ -// Copyright (c) 2020 Pantor. All rights reserved. +/* + ___ _ Version 3.3 + |_ _|_ __ (_) __ _ https://github.com/pantor/inja + | || '_ \ | |/ _` | Licensed under the MIT License . + | || | | || | (_| | + |___|_| |_|/ |\__,_| Copyright (c) 2018-2021 Lars Berscheid + |__/ +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +*/ #ifndef INCLUDE_INJA_INJA_HPP_ #define INCLUDE_INJA_INJA_HPP_ #include +namespace inja { +#ifndef INJA_DATA_TYPE +using json = nlohmann::json; +#else +using json = INJA_DATA_TYPE; +#endif +} // namespace inja + #if (defined(__cpp_exceptions) || defined(__EXCEPTIONS) || defined(_CPPUNWIND)) && !defined(INJA_NOEXCEPTION) - #define INJA_THROW(exception) throw exception +#ifndef INJA_THROW +#define INJA_THROW(exception) throw exception +#endif #else - #include - #define INJA_THROW(exception) std::abort() +#include +#ifndef INJA_THROW +#define INJA_THROW(exception) \ + std::abort(); \ + std::ignore = exception +#endif +#ifndef INJA_NOEXCEPTION +#define INJA_NOEXCEPTION +#endif #endif // #include "environment.hpp" -// Copyright (c) 2019 Pantor. All rights reserved. - #ifndef INCLUDE_INJA_ENVIRONMENT_HPP_ #define INCLUDE_INJA_ENVIRONMENT_HPP_ @@ -23,2751 +60,1387 @@ #include #include #include - -#include +#include // #include "config.hpp" -// Copyright (c) 2019 Pantor. All rights reserved. - #ifndef INCLUDE_INJA_CONFIG_HPP_ #define INCLUDE_INJA_CONFIG_HPP_ #include #include -// #include "string_view.hpp" -// Copyright 2017-2019 by Martin Moene -// -// string-view lite, a C++17-like string_view for C++98 and later. -// For more information see https://github.com/martinmoene/string-view-lite -// -// Distributed under the Boost Software License, Version 1.0. -// (See accompanying file LICENSE.txt or copy at http://www.boost.org/LICENSE_1_0.txt) - - +// #include "template.hpp" +#ifndef INCLUDE_INJA_TEMPLATE_HPP_ +#define INCLUDE_INJA_TEMPLATE_HPP_ -#ifndef NONSTD_SV_LITE_H_INCLUDED -#define NONSTD_SV_LITE_H_INCLUDED +#include +#include +#include +#include -#define string_view_lite_MAJOR 1 -#define string_view_lite_MINOR 4 -#define string_view_lite_PATCH 0 +// #include "node.hpp" +#ifndef INCLUDE_INJA_NODE_HPP_ +#define INCLUDE_INJA_NODE_HPP_ -#define string_view_lite_VERSION \ - nssv_STRINGIFY(string_view_lite_MAJOR) "." nssv_STRINGIFY(string_view_lite_MINOR) "." nssv_STRINGIFY( \ - string_view_lite_PATCH) +#include +#include +#include -#define nssv_STRINGIFY(x) nssv_STRINGIFY_(x) -#define nssv_STRINGIFY_(x) #x +// #include "function_storage.hpp" +#ifndef INCLUDE_INJA_FUNCTION_STORAGE_HPP_ +#define INCLUDE_INJA_FUNCTION_STORAGE_HPP_ -// string-view lite configuration: +#include +#include -#define nssv_STRING_VIEW_DEFAULT 0 -#define nssv_STRING_VIEW_NONSTD 1 -#define nssv_STRING_VIEW_STD 2 +namespace inja { -#if !defined(nssv_CONFIG_SELECT_STRING_VIEW) -#define nssv_CONFIG_SELECT_STRING_VIEW (nssv_HAVE_STD_STRING_VIEW ? nssv_STRING_VIEW_STD : nssv_STRING_VIEW_NONSTD) -#endif +using Arguments = std::vector; +using CallbackFunction = std::function; +using VoidCallbackFunction = std::function; -#if defined(nssv_CONFIG_SELECT_STD_STRING_VIEW) || defined(nssv_CONFIG_SELECT_NONSTD_STRING_VIEW) -#error nssv_CONFIG_SELECT_STD_STRING_VIEW and nssv_CONFIG_SELECT_NONSTD_STRING_VIEW are deprecated and removed, please use nssv_CONFIG_SELECT_STRING_VIEW=nssv_STRING_VIEW_... -#endif +/*! + * \brief Class for builtin functions and user-defined callbacks. + */ +class FunctionStorage { +public: + enum class Operation { + Not, + And, + Or, + In, + Equal, + NotEqual, + Greater, + GreaterEqual, + Less, + LessEqual, + Add, + Subtract, + Multiplication, + Division, + Power, + Modulo, + AtId, + At, + Default, + DivisibleBy, + Even, + Exists, + ExistsInObject, + First, + Float, + Int, + IsArray, + IsBoolean, + IsFloat, + IsInteger, + IsNumber, + IsObject, + IsString, + Last, + Length, + Lower, + Max, + Min, + Odd, + Range, + Round, + Sort, + Upper, + Super, + Join, + Callback, + ParenLeft, + ParenRight, + None, + }; -#ifndef nssv_CONFIG_STD_SV_OPERATOR -#define nssv_CONFIG_STD_SV_OPERATOR 0 -#endif + struct FunctionData { + explicit FunctionData(const Operation& op, const CallbackFunction& cb = CallbackFunction {}): operation(op), callback(cb) {} + const Operation operation; + const CallbackFunction callback; + }; -#ifndef nssv_CONFIG_USR_SV_OPERATOR -#define nssv_CONFIG_USR_SV_OPERATOR 1 -#endif +private: + const int VARIADIC {-1}; -#ifdef nssv_CONFIG_CONVERSION_STD_STRING -#define nssv_CONFIG_CONVERSION_STD_STRING_CLASS_METHODS nssv_CONFIG_CONVERSION_STD_STRING -#define nssv_CONFIG_CONVERSION_STD_STRING_FREE_FUNCTIONS nssv_CONFIG_CONVERSION_STD_STRING -#endif + std::map, FunctionData> function_storage = { + {std::make_pair("at", 2), FunctionData {Operation::At}}, + {std::make_pair("default", 2), FunctionData {Operation::Default}}, + {std::make_pair("divisibleBy", 2), FunctionData {Operation::DivisibleBy}}, + {std::make_pair("even", 1), FunctionData {Operation::Even}}, + {std::make_pair("exists", 1), FunctionData {Operation::Exists}}, + {std::make_pair("existsIn", 2), FunctionData {Operation::ExistsInObject}}, + {std::make_pair("first", 1), FunctionData {Operation::First}}, + {std::make_pair("float", 1), FunctionData {Operation::Float}}, + {std::make_pair("int", 1), FunctionData {Operation::Int}}, + {std::make_pair("isArray", 1), FunctionData {Operation::IsArray}}, + {std::make_pair("isBoolean", 1), FunctionData {Operation::IsBoolean}}, + {std::make_pair("isFloat", 1), FunctionData {Operation::IsFloat}}, + {std::make_pair("isInteger", 1), FunctionData {Operation::IsInteger}}, + {std::make_pair("isNumber", 1), FunctionData {Operation::IsNumber}}, + {std::make_pair("isObject", 1), FunctionData {Operation::IsObject}}, + {std::make_pair("isString", 1), FunctionData {Operation::IsString}}, + {std::make_pair("last", 1), FunctionData {Operation::Last}}, + {std::make_pair("length", 1), FunctionData {Operation::Length}}, + {std::make_pair("lower", 1), FunctionData {Operation::Lower}}, + {std::make_pair("max", 1), FunctionData {Operation::Max}}, + {std::make_pair("min", 1), FunctionData {Operation::Min}}, + {std::make_pair("odd", 1), FunctionData {Operation::Odd}}, + {std::make_pair("range", 1), FunctionData {Operation::Range}}, + {std::make_pair("round", 2), FunctionData {Operation::Round}}, + {std::make_pair("sort", 1), FunctionData {Operation::Sort}}, + {std::make_pair("upper", 1), FunctionData {Operation::Upper}}, + {std::make_pair("super", 0), FunctionData {Operation::Super}}, + {std::make_pair("super", 1), FunctionData {Operation::Super}}, + {std::make_pair("join", 2), FunctionData {Operation::Join}}, + }; -#ifndef nssv_CONFIG_CONVERSION_STD_STRING_CLASS_METHODS -#define nssv_CONFIG_CONVERSION_STD_STRING_CLASS_METHODS 1 -#endif +public: + void add_builtin(std::string_view name, int num_args, Operation op) { + function_storage.emplace(std::make_pair(static_cast(name), num_args), FunctionData {op}); + } -#ifndef nssv_CONFIG_CONVERSION_STD_STRING_FREE_FUNCTIONS -#define nssv_CONFIG_CONVERSION_STD_STRING_FREE_FUNCTIONS 1 -#endif + void add_callback(std::string_view name, int num_args, const CallbackFunction& callback) { + function_storage.emplace(std::make_pair(static_cast(name), num_args), FunctionData {Operation::Callback, callback}); + } -// Control presence of exception handling (try and auto discover): + FunctionData find_function(std::string_view name, int num_args) const { + auto it = function_storage.find(std::make_pair(static_cast(name), num_args)); + if (it != function_storage.end()) { + return it->second; -#ifndef nssv_CONFIG_NO_EXCEPTIONS -#if defined(__cpp_exceptions) || defined(__EXCEPTIONS) || defined(_CPPUNWIND) -#define nssv_CONFIG_NO_EXCEPTIONS 0 -#else -#define nssv_CONFIG_NO_EXCEPTIONS 1 -#endif -#endif + // Find variadic function + } else if (num_args > 0) { + it = function_storage.find(std::make_pair(static_cast(name), VARIADIC)); + if (it != function_storage.end()) { + return it->second; + } + } -// C++ language version detection (C++20 is speculative): -// Note: VC14.0/1900 (VS2015) lacks too much from C++14. + return FunctionData {Operation::None}; + } +}; -#ifndef nssv_CPLUSPLUS -#if defined(_MSVC_LANG) && !defined(__clang__) -#define nssv_CPLUSPLUS (_MSC_VER == 1900 ? 201103L : _MSVC_LANG) -#else -#define nssv_CPLUSPLUS __cplusplus -#endif -#endif +} // namespace inja -#define nssv_CPP98_OR_GREATER (nssv_CPLUSPLUS >= 199711L) -#define nssv_CPP11_OR_GREATER (nssv_CPLUSPLUS >= 201103L) -#define nssv_CPP11_OR_GREATER_ (nssv_CPLUSPLUS >= 201103L) -#define nssv_CPP14_OR_GREATER (nssv_CPLUSPLUS >= 201402L) -#define nssv_CPP17_OR_GREATER (nssv_CPLUSPLUS >= 201703L) -#define nssv_CPP20_OR_GREATER (nssv_CPLUSPLUS >= 202000L) +#endif // INCLUDE_INJA_FUNCTION_STORAGE_HPP_ -// use C++17 std::string_view if available and requested: +// #include "utils.hpp" +#ifndef INCLUDE_INJA_UTILS_HPP_ +#define INCLUDE_INJA_UTILS_HPP_ -#if nssv_CPP17_OR_GREATER && defined(__has_include) -#if __has_include( ) -#define nssv_HAVE_STD_STRING_VIEW 1 -#else -#define nssv_HAVE_STD_STRING_VIEW 0 -#endif -#else -#define nssv_HAVE_STD_STRING_VIEW 0 -#endif +#include +#include +#include +#include +#include -#define nssv_USES_STD_STRING_VIEW \ - ((nssv_CONFIG_SELECT_STRING_VIEW == nssv_STRING_VIEW_STD) || \ - ((nssv_CONFIG_SELECT_STRING_VIEW == nssv_STRING_VIEW_DEFAULT) && nssv_HAVE_STD_STRING_VIEW)) +// #include "exceptions.hpp" +#ifndef INCLUDE_INJA_EXCEPTIONS_HPP_ +#define INCLUDE_INJA_EXCEPTIONS_HPP_ -#define nssv_HAVE_STARTS_WITH (nssv_CPP20_OR_GREATER || !nssv_USES_STD_STRING_VIEW) -#define nssv_HAVE_ENDS_WITH nssv_HAVE_STARTS_WITH +#include +#include -// -// Use C++17 std::string_view: -// +namespace inja { -#if nssv_USES_STD_STRING_VIEW +struct SourceLocation { + size_t line; + size_t column; +}; -#include +struct InjaError : public std::runtime_error { + const std::string type; + const std::string message; -// Extensions for std::string: + const SourceLocation location; -#if nssv_CONFIG_CONVERSION_STD_STRING_FREE_FUNCTIONS + explicit InjaError(const std::string& type, const std::string& message) + : std::runtime_error("[inja.exception." + type + "] " + message), type(type), message(message), location({0, 0}) {} -namespace nonstd { + explicit InjaError(const std::string& type, const std::string& message, SourceLocation location) + : std::runtime_error("[inja.exception." + type + "] (at " + std::to_string(location.line) + ":" + std::to_string(location.column) + ") " + message), + type(type), message(message), location(location) {} +}; -template > -std::basic_string to_string(std::basic_string_view v, - Allocator const &a = Allocator()) { - return std::basic_string(v.begin(), v.end(), a); -} +struct ParserError : public InjaError { + explicit ParserError(const std::string& message, SourceLocation location): InjaError("parser_error", message, location) {} +}; -template -std::basic_string_view to_string_view(std::basic_string const &s) { - return std::basic_string_view(s.data(), s.size()); -} +struct RenderError : public InjaError { + explicit RenderError(const std::string& message, SourceLocation location): InjaError("render_error", message, location) {} +}; -// Literal operators sv and _sv: +struct FileError : public InjaError { + explicit FileError(const std::string& message): InjaError("file_error", message) {} + explicit FileError(const std::string& message, SourceLocation location): InjaError("file_error", message, location) {} +}; -#if nssv_CONFIG_STD_SV_OPERATOR +struct DataError : public InjaError { + explicit DataError(const std::string& message, SourceLocation location): InjaError("data_error", message, location) {} +}; -using namespace std::literals::string_view_literals; +} // namespace inja -#endif +#endif // INCLUDE_INJA_EXCEPTIONS_HPP_ -#if nssv_CONFIG_USR_SV_OPERATOR -inline namespace literals { -inline namespace string_view_literals { +namespace inja { -constexpr std::string_view operator"" _sv(const char *str, size_t len) noexcept // (1) -{ - return std::string_view {str, len}; +namespace string_view { +inline std::string_view slice(std::string_view view, size_t start, size_t end) { + start = std::min(start, view.size()); + end = std::min(std::max(start, end), view.size()); + return view.substr(start, end - start); } -constexpr std::u16string_view operator"" _sv(const char16_t *str, size_t len) noexcept // (2) -{ - return std::u16string_view {str, len}; +inline std::pair split(std::string_view view, char Separator) { + size_t idx = view.find(Separator); + if (idx == std::string_view::npos) { + return std::make_pair(view, std::string_view()); + } + return std::make_pair(slice(view, 0, idx), slice(view, idx + 1, std::string_view::npos)); } -constexpr std::u32string_view operator"" _sv(const char32_t *str, size_t len) noexcept // (3) -{ - return std::u32string_view {str, len}; +inline bool starts_with(std::string_view view, std::string_view prefix) { + return (view.size() >= prefix.size() && view.compare(0, prefix.size(), prefix) == 0); } +} // namespace string_view -constexpr std::wstring_view operator"" _sv(const wchar_t *str, size_t len) noexcept // (4) -{ - return std::wstring_view {str, len}; -} +inline SourceLocation get_source_location(std::string_view content, size_t pos) { + // Get line and offset position (starts at 1:1) + auto sliced = string_view::slice(content, 0, pos); + std::size_t last_newline = sliced.rfind("\n"); -} // namespace string_view_literals -} // namespace literals + if (last_newline == std::string_view::npos) { + return {1, sliced.length() + 1}; + } -#endif // nssv_CONFIG_USR_SV_OPERATOR + // Count newlines + size_t count_lines = 0; + size_t search_start = 0; + while (search_start <= sliced.size()) { + search_start = sliced.find("\n", search_start) + 1; + if (search_start == 0) { + break; + } + count_lines += 1; + } -} // namespace nonstd + return {count_lines + 1, sliced.length() - last_newline}; +} -#endif // nssv_CONFIG_CONVERSION_STD_STRING_FREE_FUNCTIONS +inline void replace_substring(std::string& s, const std::string& f, const std::string& t) { + if (f.empty()) { + return; + } + for (auto pos = s.find(f); // find first occurrence of f + pos != std::string::npos; // make sure f was found + s.replace(pos, f.size(), t), // replace with t, and + pos = s.find(f, pos + t.size())) // find next occurrence of f + {} +} -namespace nonstd { +} // namespace inja -using std::basic_string_view; -using std::string_view; -using std::u16string_view; -using std::u32string_view; -using std::wstring_view; +#endif // INCLUDE_INJA_UTILS_HPP_ -// literal "sv" and "_sv", see above -using std::operator==; -using std::operator!=; -using std::operator<; -using std::operator<=; -using std::operator>; -using std::operator>=; +namespace inja { -using std::operator<<; +class NodeVisitor; +class BlockNode; +class TextNode; +class ExpressionNode; +class LiteralNode; +class DataNode; +class FunctionNode; +class ExpressionListNode; +class StatementNode; +class ForStatementNode; +class ForArrayStatementNode; +class ForObjectStatementNode; +class IfStatementNode; +class IncludeStatementNode; +class ExtendsStatementNode; +class BlockStatementNode; +class SetStatementNode; -} // namespace nonstd +class NodeVisitor { +public: + virtual ~NodeVisitor() = default; -#else // nssv_HAVE_STD_STRING_VIEW + virtual void visit(const BlockNode& node) = 0; + virtual void visit(const TextNode& node) = 0; + virtual void visit(const ExpressionNode& node) = 0; + virtual void visit(const LiteralNode& node) = 0; + virtual void visit(const DataNode& node) = 0; + virtual void visit(const FunctionNode& node) = 0; + virtual void visit(const ExpressionListNode& node) = 0; + virtual void visit(const StatementNode& node) = 0; + virtual void visit(const ForStatementNode& node) = 0; + virtual void visit(const ForArrayStatementNode& node) = 0; + virtual void visit(const ForObjectStatementNode& node) = 0; + virtual void visit(const IfStatementNode& node) = 0; + virtual void visit(const IncludeStatementNode& node) = 0; + virtual void visit(const ExtendsStatementNode& node) = 0; + virtual void visit(const BlockStatementNode& node) = 0; + virtual void visit(const SetStatementNode& node) = 0; +}; -// -// Before C++17: use string_view lite: -// +/*! + * \brief Base node class for the abstract syntax tree (AST). + */ +class AstNode { +public: + virtual void accept(NodeVisitor& v) const = 0; -// Compiler versions: -// -// MSVC++ 6.0 _MSC_VER == 1200 (Visual Studio 6.0) -// MSVC++ 7.0 _MSC_VER == 1300 (Visual Studio .NET 2002) -// MSVC++ 7.1 _MSC_VER == 1310 (Visual Studio .NET 2003) -// MSVC++ 8.0 _MSC_VER == 1400 (Visual Studio 2005) -// MSVC++ 9.0 _MSC_VER == 1500 (Visual Studio 2008) -// MSVC++ 10.0 _MSC_VER == 1600 (Visual Studio 2010) -// MSVC++ 11.0 _MSC_VER == 1700 (Visual Studio 2012) -// MSVC++ 12.0 _MSC_VER == 1800 (Visual Studio 2013) -// MSVC++ 14.0 _MSC_VER == 1900 (Visual Studio 2015) -// MSVC++ 14.1 _MSC_VER >= 1910 (Visual Studio 2017) + size_t pos; -#if defined(_MSC_VER) && !defined(__clang__) -#define nssv_COMPILER_MSVC_VER (_MSC_VER) -#define nssv_COMPILER_MSVC_VERSION (_MSC_VER / 10 - 10 * (5 + (_MSC_VER < 1900))) -#else -#define nssv_COMPILER_MSVC_VER 0 -#define nssv_COMPILER_MSVC_VERSION 0 -#endif + AstNode(size_t pos): pos(pos) {} + virtual ~AstNode() {} +}; -#define nssv_COMPILER_VERSION(major, minor, patch) (10 * (10 * (major) + (minor)) + (patch)) +class BlockNode : public AstNode { +public: + std::vector> nodes; -#if defined(__clang__) -#define nssv_COMPILER_CLANG_VERSION nssv_COMPILER_VERSION(__clang_major__, __clang_minor__, __clang_patchlevel__) -#else -#define nssv_COMPILER_CLANG_VERSION 0 -#endif + explicit BlockNode(): AstNode(0) {} -#if defined(__GNUC__) && !defined(__clang__) -#define nssv_COMPILER_GNUC_VERSION nssv_COMPILER_VERSION(__GNUC__, __GNUC_MINOR__, __GNUC_PATCHLEVEL__) -#else -#define nssv_COMPILER_GNUC_VERSION 0 -#endif + void accept(NodeVisitor& v) const { + v.visit(*this); + } +}; -// half-open range [lo..hi): -#define nssv_BETWEEN(v, lo, hi) ((lo) <= (v) && (v) < (hi)) +class TextNode : public AstNode { +public: + const size_t length; -// Presence of language and library features: + explicit TextNode(size_t pos, size_t length): AstNode(pos), length(length) {} -#ifdef _HAS_CPP0X -#define nssv_HAS_CPP0X _HAS_CPP0X -#else -#define nssv_HAS_CPP0X 0 -#endif - -// Unless defined otherwise below, consider VC14 as C++11 for variant-lite: - -#if nssv_COMPILER_MSVC_VER >= 1900 -#undef nssv_CPP11_OR_GREATER -#define nssv_CPP11_OR_GREATER 1 -#endif - -#define nssv_CPP11_90 (nssv_CPP11_OR_GREATER_ || nssv_COMPILER_MSVC_VER >= 1500) -#define nssv_CPP11_100 (nssv_CPP11_OR_GREATER_ || nssv_COMPILER_MSVC_VER >= 1600) -#define nssv_CPP11_110 (nssv_CPP11_OR_GREATER_ || nssv_COMPILER_MSVC_VER >= 1700) -#define nssv_CPP11_120 (nssv_CPP11_OR_GREATER_ || nssv_COMPILER_MSVC_VER >= 1800) -#define nssv_CPP11_140 (nssv_CPP11_OR_GREATER_ || nssv_COMPILER_MSVC_VER >= 1900) -#define nssv_CPP11_141 (nssv_CPP11_OR_GREATER_ || nssv_COMPILER_MSVC_VER >= 1910) - -#define nssv_CPP14_000 (nssv_CPP14_OR_GREATER) -#define nssv_CPP17_000 (nssv_CPP17_OR_GREATER) - -// Presence of C++11 language features: - -#define nssv_HAVE_CONSTEXPR_11 nssv_CPP11_140 -#define nssv_HAVE_EXPLICIT_CONVERSION nssv_CPP11_140 -#define nssv_HAVE_INLINE_NAMESPACE nssv_CPP11_140 -#define nssv_HAVE_NOEXCEPT nssv_CPP11_140 -#define nssv_HAVE_NULLPTR nssv_CPP11_100 -#define nssv_HAVE_REF_QUALIFIER nssv_CPP11_140 -#define nssv_HAVE_UNICODE_LITERALS nssv_CPP11_140 -#define nssv_HAVE_USER_DEFINED_LITERALS nssv_CPP11_140 -#define nssv_HAVE_WCHAR16_T nssv_CPP11_100 -#define nssv_HAVE_WCHAR32_T nssv_CPP11_100 - -#if !((nssv_CPP11_OR_GREATER && nssv_COMPILER_CLANG_VERSION) || nssv_BETWEEN(nssv_COMPILER_CLANG_VERSION, 300, 400)) -#define nssv_HAVE_STD_DEFINED_LITERALS nssv_CPP11_140 -#else -#define nssv_HAVE_STD_DEFINED_LITERALS 0 -#endif - -// Presence of C++14 language features: + void accept(NodeVisitor& v) const { + v.visit(*this); + } +}; -#define nssv_HAVE_CONSTEXPR_14 nssv_CPP14_000 +class ExpressionNode : public AstNode { +public: + explicit ExpressionNode(size_t pos): AstNode(pos) {} -// Presence of C++17 language features: + void accept(NodeVisitor& v) const { + v.visit(*this); + } +}; -#define nssv_HAVE_NODISCARD nssv_CPP17_000 +class LiteralNode : public ExpressionNode { +public: + const json value; -// Presence of C++ library features: + explicit LiteralNode(std::string_view data_text, size_t pos): ExpressionNode(pos), value(json::parse(data_text)) {} -#define nssv_HAVE_STD_HASH nssv_CPP11_120 + void accept(NodeVisitor& v) const { + v.visit(*this); + } +}; -// C++ feature usage: +class DataNode : public ExpressionNode { +public: + const std::string name; + const json::json_pointer ptr; -#if nssv_HAVE_CONSTEXPR_11 -#define nssv_constexpr constexpr -#else -#define nssv_constexpr /*constexpr*/ -#endif + static std::string convert_dot_to_ptr(std::string_view ptr_name) { + std::string result; + do { + std::string_view part; + std::tie(part, ptr_name) = string_view::split(ptr_name, '.'); + result.push_back('/'); + result.append(part.begin(), part.end()); + } while (!ptr_name.empty()); + return result; + } -#if nssv_HAVE_CONSTEXPR_14 -#define nssv_constexpr14 constexpr -#else -#define nssv_constexpr14 /*constexpr*/ -#endif + explicit DataNode(std::string_view ptr_name, size_t pos): ExpressionNode(pos), name(ptr_name), ptr(json::json_pointer(convert_dot_to_ptr(ptr_name))) {} -#if nssv_HAVE_EXPLICIT_CONVERSION -#define nssv_explicit explicit -#else -#define nssv_explicit /*explicit*/ -#endif + void accept(NodeVisitor& v) const { + v.visit(*this); + } +}; -#if nssv_HAVE_INLINE_NAMESPACE -#define nssv_inline_ns inline -#else -#define nssv_inline_ns /*inline*/ -#endif +class FunctionNode : public ExpressionNode { + using Op = FunctionStorage::Operation; -#if nssv_HAVE_NOEXCEPT -#define nssv_noexcept noexcept -#else -#define nssv_noexcept /*noexcept*/ -#endif +public: + enum class Associativity { + Left, + Right, + }; -//#if nssv_HAVE_REF_QUALIFIER -//# define nssv_ref_qual & -//# define nssv_refref_qual && -//#else -//# define nssv_ref_qual /*&*/ -//# define nssv_refref_qual /*&&*/ -//#endif + unsigned int precedence; + Associativity associativity; -#if nssv_HAVE_NULLPTR -#define nssv_nullptr nullptr -#else -#define nssv_nullptr NULL -#endif + Op operation; -#if nssv_HAVE_NODISCARD -#define nssv_nodiscard [[nodiscard]] -#else -#define nssv_nodiscard /*[[nodiscard]]*/ -#endif + std::string name; + int number_args; // Should also be negative -> -1 for unknown number + std::vector> arguments; + CallbackFunction callback; -// Additional includes: + explicit FunctionNode(std::string_view name, size_t pos) + : ExpressionNode(pos), precedence(8), associativity(Associativity::Left), operation(Op::Callback), name(name), number_args(1) {} + explicit FunctionNode(Op operation, size_t pos): ExpressionNode(pos), operation(operation), number_args(1) { + switch (operation) { + case Op::Not: { + number_args = 1; + precedence = 4; + associativity = Associativity::Left; + } break; + case Op::And: { + number_args = 2; + precedence = 1; + associativity = Associativity::Left; + } break; + case Op::Or: { + number_args = 2; + precedence = 1; + associativity = Associativity::Left; + } break; + case Op::In: { + number_args = 2; + precedence = 2; + associativity = Associativity::Left; + } break; + case Op::Equal: { + number_args = 2; + precedence = 2; + associativity = Associativity::Left; + } break; + case Op::NotEqual: { + number_args = 2; + precedence = 2; + associativity = Associativity::Left; + } break; + case Op::Greater: { + number_args = 2; + precedence = 2; + associativity = Associativity::Left; + } break; + case Op::GreaterEqual: { + number_args = 2; + precedence = 2; + associativity = Associativity::Left; + } break; + case Op::Less: { + number_args = 2; + precedence = 2; + associativity = Associativity::Left; + } break; + case Op::LessEqual: { + number_args = 2; + precedence = 2; + associativity = Associativity::Left; + } break; + case Op::Add: { + number_args = 2; + precedence = 3; + associativity = Associativity::Left; + } break; + case Op::Subtract: { + number_args = 2; + precedence = 3; + associativity = Associativity::Left; + } break; + case Op::Multiplication: { + number_args = 2; + precedence = 4; + associativity = Associativity::Left; + } break; + case Op::Division: { + number_args = 2; + precedence = 4; + associativity = Associativity::Left; + } break; + case Op::Power: { + number_args = 2; + precedence = 5; + associativity = Associativity::Right; + } break; + case Op::Modulo: { + number_args = 2; + precedence = 4; + associativity = Associativity::Left; + } break; + case Op::AtId: { + number_args = 2; + precedence = 8; + associativity = Associativity::Left; + } break; + default: { + precedence = 1; + associativity = Associativity::Left; + } + } + } -#include -#include -#include -#include -#include -#include // std::char_traits<> + void accept(NodeVisitor& v) const { + v.visit(*this); + } +}; -#if !nssv_CONFIG_NO_EXCEPTIONS -#include -#endif +class ExpressionListNode : public AstNode { +public: + std::shared_ptr root; -#if nssv_CPP11_OR_GREATER -#include -#endif + explicit ExpressionListNode(): AstNode(0) {} + explicit ExpressionListNode(size_t pos): AstNode(pos) {} -// Clang, GNUC, MSVC warning suppression macros: - -#if defined(__clang__) -#pragma clang diagnostic ignored "-Wreserved-user-defined-literal" -#pragma clang diagnostic push -#pragma clang diagnostic ignored "-Wuser-defined-literals" -#elif defined(__GNUC__) -#pragma GCC diagnostic push -#pragma GCC diagnostic ignored "-Wliteral-suffix" -#endif // __clang__ - -#if nssv_COMPILER_MSVC_VERSION >= 140 -#define nssv_SUPPRESS_MSGSL_WARNING(expr) [[gsl::suppress(expr)]] -#define nssv_SUPPRESS_MSVC_WARNING(code, descr) __pragma(warning(suppress : code)) -#define nssv_DISABLE_MSVC_WARNINGS(codes) __pragma(warning(push)) __pragma(warning(disable : codes)) -#else -#define nssv_SUPPRESS_MSGSL_WARNING(expr) -#define nssv_SUPPRESS_MSVC_WARNING(code, descr) -#define nssv_DISABLE_MSVC_WARNINGS(codes) -#endif + void accept(NodeVisitor& v) const { + v.visit(*this); + } +}; -#if defined(__clang__) -#define nssv_RESTORE_WARNINGS() _Pragma("clang diagnostic pop") -#elif defined(__GNUC__) -#define nssv_RESTORE_WARNINGS() _Pragma("GCC diagnostic pop") -#elif nssv_COMPILER_MSVC_VERSION >= 140 -#define nssv_RESTORE_WARNINGS() __pragma(warning(pop)) -#else -#define nssv_RESTORE_WARNINGS() -#endif +class StatementNode : public AstNode { +public: + StatementNode(size_t pos): AstNode(pos) {} -// Suppress the following MSVC (GSL) warnings: -// - C4455, non-gsl : 'operator ""sv': literal suffix identifiers that do not -// start with an underscore are reserved -// - C26472, gsl::t.1 : don't use a static_cast for arithmetic conversions; -// use brace initialization, gsl::narrow_cast or gsl::narow -// - C26481: gsl::b.1 : don't use pointer arithmetic. Use span instead + virtual void accept(NodeVisitor& v) const = 0; +}; -nssv_DISABLE_MSVC_WARNINGS(4455 26481 26472) - // nssv_DISABLE_CLANG_WARNINGS( "-Wuser-defined-literals" ) - // nssv_DISABLE_GNUC_WARNINGS( -Wliteral-suffix ) +class ForStatementNode : public StatementNode { +public: + ExpressionListNode condition; + BlockNode body; + BlockNode* const parent; - namespace nonstd { - namespace sv_lite { + ForStatementNode(BlockNode* const parent, size_t pos): StatementNode(pos), parent(parent) {} -#if nssv_CPP11_OR_GREATER + virtual void accept(NodeVisitor& v) const = 0; +}; - namespace detail { +class ForArrayStatementNode : public ForStatementNode { +public: + const std::string value; - // Expect tail call optimization to make length() non-recursive: + explicit ForArrayStatementNode(const std::string& value, BlockNode* const parent, size_t pos): ForStatementNode(parent, pos), value(value) {} - template inline constexpr std::size_t length(CharT *s, std::size_t result = 0) { - return *s == '\0' ? result : length(s + 1, result + 1); + void accept(NodeVisitor& v) const { + v.visit(*this); } +}; - } // namespace detail +class ForObjectStatementNode : public ForStatementNode { +public: + const std::string key; + const std::string value; -#endif // nssv_CPP11_OR_GREATER + explicit ForObjectStatementNode(const std::string& key, const std::string& value, BlockNode* const parent, size_t pos) + : ForStatementNode(parent, pos), key(key), value(value) {} - template > class basic_string_view; + void accept(NodeVisitor& v) const { + v.visit(*this); + } +}; - // - // basic_string_view: - // +class IfStatementNode : public StatementNode { +public: + ExpressionListNode condition; + BlockNode true_statement; + BlockNode false_statement; + BlockNode* const parent; - template */ - > - class basic_string_view { - public: - // Member types: + const bool is_nested; + bool has_false_statement {false}; - typedef Traits traits_type; - typedef CharT value_type; + explicit IfStatementNode(BlockNode* const parent, size_t pos): StatementNode(pos), parent(parent), is_nested(false) {} + explicit IfStatementNode(bool is_nested, BlockNode* const parent, size_t pos): StatementNode(pos), parent(parent), is_nested(is_nested) {} - typedef CharT *pointer; - typedef CharT const *const_pointer; - typedef CharT &reference; - typedef CharT const &const_reference; + void accept(NodeVisitor& v) const { + v.visit(*this); + } +}; - typedef const_pointer iterator; - typedef const_pointer const_iterator; - typedef std::reverse_iterator reverse_iterator; - typedef std::reverse_iterator const_reverse_iterator; +class IncludeStatementNode : public StatementNode { +public: + const std::string file; - typedef std::size_t size_type; - typedef std::ptrdiff_t difference_type; + explicit IncludeStatementNode(const std::string& file, size_t pos): StatementNode(pos), file(file) {} - // 24.4.2.1 Construction and assignment: + void accept(NodeVisitor& v) const { + v.visit(*this); + } +}; - nssv_constexpr basic_string_view() nssv_noexcept : data_(nssv_nullptr), size_(0) {} +class ExtendsStatementNode : public StatementNode { +public: + const std::string file; -#if nssv_CPP11_OR_GREATER - nssv_constexpr basic_string_view(basic_string_view const &other) nssv_noexcept = default; -#else - nssv_constexpr basic_string_view(basic_string_view const &other) nssv_noexcept : data_(other.data_), - size_(other.size_) {} -#endif + explicit ExtendsStatementNode(const std::string& file, size_t pos): StatementNode(pos), file(file) {} - nssv_constexpr basic_string_view(CharT const *s, size_type count) nssv_noexcept // non-standard noexcept - : data_(s), - size_(count) {} - - nssv_constexpr basic_string_view(CharT const *s) nssv_noexcept // non-standard noexcept - : data_(s) -#if nssv_CPP17_OR_GREATER - , - size_(Traits::length(s)) -#elif nssv_CPP11_OR_GREATER - , - size_(detail::length(s)) -#else - , - size_(Traits::length(s)) -#endif - { - } + void accept(NodeVisitor& v) const { + v.visit(*this); + }; +}; - // Assignment: +class BlockStatementNode : public StatementNode { +public: + const std::string name; + BlockNode block; + BlockNode* const parent; -#if nssv_CPP11_OR_GREATER - nssv_constexpr14 basic_string_view &operator=(basic_string_view const &other) nssv_noexcept = default; -#else - nssv_constexpr14 basic_string_view &operator=(basic_string_view const &other) nssv_noexcept { - data_ = other.data_; - size_ = other.size_; - return *this; - } -#endif + explicit BlockStatementNode(BlockNode* const parent, const std::string& name, size_t pos): StatementNode(pos), name(name), parent(parent) {} - // 24.4.2.2 Iterator support: + void accept(NodeVisitor& v) const { + v.visit(*this); + }; +}; - nssv_constexpr const_iterator begin() const nssv_noexcept { return data_; } - nssv_constexpr const_iterator end() const nssv_noexcept { return data_ + size_; } +class SetStatementNode : public StatementNode { +public: + const std::string key; + ExpressionListNode expression; - nssv_constexpr const_iterator cbegin() const nssv_noexcept { return begin(); } - nssv_constexpr const_iterator cend() const nssv_noexcept { return end(); } + explicit SetStatementNode(const std::string& key, size_t pos): StatementNode(pos), key(key) {} - nssv_constexpr const_reverse_iterator rbegin() const nssv_noexcept { return const_reverse_iterator(end()); } - nssv_constexpr const_reverse_iterator rend() const nssv_noexcept { return const_reverse_iterator(begin()); } + void accept(NodeVisitor& v) const { + v.visit(*this); + } +}; - nssv_constexpr const_reverse_iterator crbegin() const nssv_noexcept { return rbegin(); } - nssv_constexpr const_reverse_iterator crend() const nssv_noexcept { return rend(); } +} // namespace inja - // 24.4.2.3 Capacity: +#endif // INCLUDE_INJA_NODE_HPP_ - nssv_constexpr size_type size() const nssv_noexcept { return size_; } - nssv_constexpr size_type length() const nssv_noexcept { return size_; } - nssv_constexpr size_type max_size() const nssv_noexcept { return (std::numeric_limits::max)(); } +// #include "statistics.hpp" +#ifndef INCLUDE_INJA_STATISTICS_HPP_ +#define INCLUDE_INJA_STATISTICS_HPP_ - // since C++20 - nssv_nodiscard nssv_constexpr bool empty() const nssv_noexcept { return 0 == size_; } +// #include "node.hpp" - // 24.4.2.4 Element access: - nssv_constexpr const_reference operator[](size_type pos) const { return data_at(pos); } +namespace inja { - nssv_constexpr14 const_reference at(size_type pos) const { -#if nssv_CONFIG_NO_EXCEPTIONS - assert(pos < size()); -#else - if (pos >= size()) { - throw std::out_of_range("nonstd::string_view::at()"); - } -#endif - return data_at(pos); +/*! + * \brief A class for counting statistics on a Template. + */ +class StatisticsVisitor : public NodeVisitor { + void visit(const BlockNode& node) { + for (auto& n : node.nodes) { + n->accept(*this); } + } - nssv_constexpr const_reference front() const { return data_at(0); } - nssv_constexpr const_reference back() const { return data_at(size() - 1); } - - nssv_constexpr const_pointer data() const nssv_noexcept { return data_; } + void visit(const TextNode&) {} + void visit(const ExpressionNode&) {} + void visit(const LiteralNode&) {} - // 24.4.2.5 Modifiers: + void visit(const DataNode&) { + variable_counter += 1; + } - nssv_constexpr14 void remove_prefix(size_type n) { - assert(n <= size()); - data_ += n; - size_ -= n; + void visit(const FunctionNode& node) { + for (auto& n : node.arguments) { + n->accept(*this); } + } - nssv_constexpr14 void remove_suffix(size_type n) { - assert(n <= size()); - size_ -= n; - } + void visit(const ExpressionListNode& node) { + node.root->accept(*this); + } - nssv_constexpr14 void swap(basic_string_view &other) nssv_noexcept { - using std::swap; - swap(data_, other.data_); - swap(size_, other.size_); - } + void visit(const StatementNode&) {} + void visit(const ForStatementNode&) {} - // 24.4.2.6 String operations: + void visit(const ForArrayStatementNode& node) { + node.condition.accept(*this); + node.body.accept(*this); + } - size_type copy(CharT *dest, size_type n, size_type pos = 0) const { -#if nssv_CONFIG_NO_EXCEPTIONS - assert(pos <= size()); -#else - if (pos > size()) { - throw std::out_of_range("nonstd::string_view::copy()"); - } -#endif - const size_type rlen = (std::min)(n, size() - pos); + void visit(const ForObjectStatementNode& node) { + node.condition.accept(*this); + node.body.accept(*this); + } - (void)Traits::copy(dest, data() + pos, rlen); + void visit(const IfStatementNode& node) { + node.condition.accept(*this); + node.true_statement.accept(*this); + node.false_statement.accept(*this); + } - return rlen; - } + void visit(const IncludeStatementNode&) {} - nssv_constexpr14 basic_string_view substr(size_type pos = 0, size_type n = npos) const { -#if nssv_CONFIG_NO_EXCEPTIONS - assert(pos <= size()); -#else - if (pos > size()) { - throw std::out_of_range("nonstd::string_view::substr()"); - } -#endif - return basic_string_view(data() + pos, (std::min)(n, size() - pos)); - } + void visit(const ExtendsStatementNode&) {} - // compare(), 6x: + void visit(const BlockStatementNode& node) { + node.block.accept(*this); + } - nssv_constexpr14 int compare(basic_string_view other) const nssv_noexcept // (1) - { - if (const int result = Traits::compare(data(), other.data(), (std::min)(size(), other.size()))) { - return result; - } + void visit(const SetStatementNode&) {} - return size() == other.size() ? 0 : size() < other.size() ? -1 : 1; - } +public: + unsigned int variable_counter; - nssv_constexpr int compare(size_type pos1, size_type n1, basic_string_view other) const // (2) - { - return substr(pos1, n1).compare(other); - } + explicit StatisticsVisitor(): variable_counter(0) {} +}; - nssv_constexpr int compare(size_type pos1, size_type n1, basic_string_view other, size_type pos2, - size_type n2) const // (3) - { - return substr(pos1, n1).compare(other.substr(pos2, n2)); - } +} // namespace inja - nssv_constexpr int compare(CharT const *s) const // (4) - { - return compare(basic_string_view(s)); - } +#endif // INCLUDE_INJA_STATISTICS_HPP_ - nssv_constexpr int compare(size_type pos1, size_type n1, CharT const *s) const // (5) - { - return substr(pos1, n1).compare(basic_string_view(s)); - } - nssv_constexpr int compare(size_type pos1, size_type n1, CharT const *s, size_type n2) const // (6) - { - return substr(pos1, n1).compare(basic_string_view(s, n2)); - } +namespace inja { - // 24.4.2.7 Searching: - - // starts_with(), 3x, since C++20: +/*! + * \brief The main inja Template. + */ +struct Template { + BlockNode root; + std::string content; + std::map> block_storage; - nssv_constexpr bool starts_with(basic_string_view v) const nssv_noexcept // (1) - { - return size() >= v.size() && compare(0, v.size(), v) == 0; - } + explicit Template() {} + explicit Template(const std::string& content): content(content) {} - nssv_constexpr bool starts_with(CharT c) const nssv_noexcept // (2) - { - return starts_with(basic_string_view(&c, 1)); - } + /// Return number of variables (total number, not distinct ones) in the template + int count_variables() { + auto statistic_visitor = StatisticsVisitor(); + root.accept(statistic_visitor); + return statistic_visitor.variable_counter; + } +}; - nssv_constexpr bool starts_with(CharT const *s) const // (3) - { - return starts_with(basic_string_view(s)); - } +using TemplateStorage = std::map; - // ends_with(), 3x, since C++20: +} // namespace inja - nssv_constexpr bool ends_with(basic_string_view v) const nssv_noexcept // (1) - { - return size() >= v.size() && compare(size() - v.size(), npos, v) == 0; - } +#endif // INCLUDE_INJA_TEMPLATE_HPP_ - nssv_constexpr bool ends_with(CharT c) const nssv_noexcept // (2) - { - return ends_with(basic_string_view(&c, 1)); - } - nssv_constexpr bool ends_with(CharT const *s) const // (3) - { - return ends_with(basic_string_view(s)); - } +namespace inja { - // find(), 4x: +/*! + * \brief Class for lexer configuration. + */ +struct LexerConfig { + std::string statement_open {"{%"}; + std::string statement_open_no_lstrip {"{%+"}; + std::string statement_open_force_lstrip {"{%-"}; + std::string statement_close {"%}"}; + std::string statement_close_force_rstrip {"-%}"}; + std::string line_statement {"##"}; + std::string expression_open {"{{"}; + std::string expression_open_force_lstrip {"{{-"}; + std::string expression_close {"}}"}; + std::string expression_close_force_rstrip {"-}}"}; + std::string comment_open {"{#"}; + std::string comment_open_force_lstrip {"{#-"}; + std::string comment_close {"#}"}; + std::string comment_close_force_rstrip {"-#}"}; + std::string open_chars {"#{"}; - nssv_constexpr14 size_type find(basic_string_view v, size_type pos = 0) const nssv_noexcept // (1) - { - return assert(v.size() == 0 || v.data() != nssv_nullptr), - pos >= size() ? npos : to_pos(std::search(cbegin() + pos, cend(), v.cbegin(), v.cend(), Traits::eq)); - } + bool trim_blocks {false}; + bool lstrip_blocks {false}; - nssv_constexpr14 size_type find(CharT c, size_type pos = 0) const nssv_noexcept // (2) - { - return find(basic_string_view(&c, 1), pos); + void update_open_chars() { + open_chars = ""; + if (open_chars.find(line_statement[0]) == std::string::npos) { + open_chars += line_statement[0]; } - - nssv_constexpr14 size_type find(CharT const *s, size_type pos, size_type n) const // (3) - { - return find(basic_string_view(s, n), pos); + if (open_chars.find(statement_open[0]) == std::string::npos) { + open_chars += statement_open[0]; } - - nssv_constexpr14 size_type find(CharT const *s, size_type pos = 0) const // (4) - { - return find(basic_string_view(s), pos); + if (open_chars.find(statement_open_no_lstrip[0]) == std::string::npos) { + open_chars += statement_open_no_lstrip[0]; } - - // rfind(), 4x: - - nssv_constexpr14 size_type rfind(basic_string_view v, size_type pos = npos) const nssv_noexcept // (1) - { - if (size() < v.size()) { - return npos; - } - - if (v.empty()) { - return (std::min)(size(), pos); - } - - const_iterator last = cbegin() + (std::min)(size() - v.size(), pos) + v.size(); - const_iterator result = std::find_end(cbegin(), last, v.cbegin(), v.cend(), Traits::eq); - - return result != last ? size_type(result - cbegin()) : npos; + if (open_chars.find(statement_open_force_lstrip[0]) == std::string::npos) { + open_chars += statement_open_force_lstrip[0]; } - - nssv_constexpr14 size_type rfind(CharT c, size_type pos = npos) const nssv_noexcept // (2) - { - return rfind(basic_string_view(&c, 1), pos); + if (open_chars.find(expression_open[0]) == std::string::npos) { + open_chars += expression_open[0]; } - - nssv_constexpr14 size_type rfind(CharT const *s, size_type pos, size_type n) const // (3) - { - return rfind(basic_string_view(s, n), pos); + if (open_chars.find(expression_open_force_lstrip[0]) == std::string::npos) { + open_chars += expression_open_force_lstrip[0]; } - - nssv_constexpr14 size_type rfind(CharT const *s, size_type pos = npos) const // (4) - { - return rfind(basic_string_view(s), pos); + if (open_chars.find(comment_open[0]) == std::string::npos) { + open_chars += comment_open[0]; } - - // find_first_of(), 4x: - - nssv_constexpr size_type find_first_of(basic_string_view v, size_type pos = 0) const nssv_noexcept // (1) - { - return pos >= size() ? npos - : to_pos(std::find_first_of(cbegin() + pos, cend(), v.cbegin(), v.cend(), Traits::eq)); + if (open_chars.find(comment_open_force_lstrip[0]) == std::string::npos) { + open_chars += comment_open_force_lstrip[0]; } + } +}; - nssv_constexpr size_type find_first_of(CharT c, size_type pos = 0) const nssv_noexcept // (2) - { - return find_first_of(basic_string_view(&c, 1), pos); - } +/*! + * \brief Class for parser configuration. + */ +struct ParserConfig { + bool search_included_templates_in_files {true}; - nssv_constexpr size_type find_first_of(CharT const *s, size_type pos, size_type n) const // (3) - { - return find_first_of(basic_string_view(s, n), pos); - } + std::function include_callback; +}; - nssv_constexpr size_type find_first_of(CharT const *s, size_type pos = 0) const // (4) - { - return find_first_of(basic_string_view(s), pos); - } +/*! + * \brief Class for render configuration. + */ +struct RenderConfig { + bool throw_at_missing_includes {true}; +}; - // find_last_of(), 4x: +} // namespace inja - nssv_constexpr size_type find_last_of(basic_string_view v, size_type pos = npos) const nssv_noexcept // (1) - { - return empty() ? npos - : pos >= size() ? find_last_of(v, size() - 1) - : to_pos(std::find_first_of(const_reverse_iterator(cbegin() + pos + 1), crend(), - v.cbegin(), v.cend(), Traits::eq)); - } +#endif // INCLUDE_INJA_CONFIG_HPP_ - nssv_constexpr size_type find_last_of(CharT c, size_type pos = npos) const nssv_noexcept // (2) - { - return find_last_of(basic_string_view(&c, 1), pos); - } +// #include "function_storage.hpp" - nssv_constexpr size_type find_last_of(CharT const *s, size_type pos, size_type count) const // (3) - { - return find_last_of(basic_string_view(s, count), pos); - } +// #include "parser.hpp" +#ifndef INCLUDE_INJA_PARSER_HPP_ +#define INCLUDE_INJA_PARSER_HPP_ - nssv_constexpr size_type find_last_of(CharT const *s, size_type pos = npos) const // (4) - { - return find_last_of(basic_string_view(s), pos); - } +#include +#include +#include +#include +#include - // find_first_not_of(), 4x: +// #include "config.hpp" - nssv_constexpr size_type find_first_not_of(basic_string_view v, size_type pos = 0) const nssv_noexcept // (1) - { - return pos >= size() ? npos : to_pos(std::find_if(cbegin() + pos, cend(), not_in_view(v))); - } +// #include "exceptions.hpp" - nssv_constexpr size_type find_first_not_of(CharT c, size_type pos = 0) const nssv_noexcept // (2) - { - return find_first_not_of(basic_string_view(&c, 1), pos); - } +// #include "function_storage.hpp" - nssv_constexpr size_type find_first_not_of(CharT const *s, size_type pos, size_type count) const // (3) - { - return find_first_not_of(basic_string_view(s, count), pos); - } +// #include "lexer.hpp" +#ifndef INCLUDE_INJA_LEXER_HPP_ +#define INCLUDE_INJA_LEXER_HPP_ - nssv_constexpr size_type find_first_not_of(CharT const *s, size_type pos = 0) const // (4) - { - return find_first_not_of(basic_string_view(s), pos); - } +#include +#include - // find_last_not_of(), 4x: +// #include "config.hpp" - nssv_constexpr size_type find_last_not_of(basic_string_view v, size_type pos = npos) const nssv_noexcept // (1) - { - return empty() ? npos - : pos >= size() - ? find_last_not_of(v, size() - 1) - : to_pos(std::find_if(const_reverse_iterator(cbegin() + pos + 1), crend(), not_in_view(v))); - } +// #include "token.hpp" +#ifndef INCLUDE_INJA_TOKEN_HPP_ +#define INCLUDE_INJA_TOKEN_HPP_ - nssv_constexpr size_type find_last_not_of(CharT c, size_type pos = npos) const nssv_noexcept // (2) - { - return find_last_not_of(basic_string_view(&c, 1), pos); - } +#include +#include - nssv_constexpr size_type find_last_not_of(CharT const *s, size_type pos, size_type count) const // (3) - { - return find_last_not_of(basic_string_view(s, count), pos); - } +namespace inja { - nssv_constexpr size_type find_last_not_of(CharT const *s, size_type pos = npos) const // (4) - { - return find_last_not_of(basic_string_view(s), pos); - } +/*! + * \brief Helper-class for the inja Lexer. + */ +struct Token { + enum class Kind { + Text, + ExpressionOpen, // {{ + ExpressionClose, // }} + LineStatementOpen, // ## + LineStatementClose, // \n + StatementOpen, // {% + StatementClose, // %} + CommentOpen, // {# + CommentClose, // #} + Id, // this, this.foo + Number, // 1, 2, -1, 5.2, -5.3 + String, // "this" + Plus, // + + Minus, // - + Times, // * + Slash, // / + Percent, // % + Power, // ^ + Comma, // , + Dot, // . + Colon, // : + LeftParen, // ( + RightParen, // ) + LeftBracket, // [ + RightBracket, // ] + LeftBrace, // { + RightBrace, // } + Equal, // == + NotEqual, // != + GreaterThan, // > + GreaterEqual, // >= + LessThan, // < + LessEqual, // <= + Unknown, + Eof, + }; - // Constants: + Kind kind {Kind::Unknown}; + std::string_view text; -#if nssv_CPP17_OR_GREATER - static nssv_constexpr size_type npos = size_type(-1); -#elif nssv_CPP11_OR_GREATER - enum : size_type { npos = size_type(-1) }; -#else - enum { npos = size_type(-1) }; -#endif + explicit constexpr Token() = default; + explicit constexpr Token(Kind kind, std::string_view text): kind(kind), text(text) {} - private: - struct not_in_view { - const basic_string_view v; + std::string describe() const { + switch (kind) { + case Kind::Text: + return ""; + case Kind::LineStatementClose: + return ""; + case Kind::Eof: + return ""; + default: + return static_cast(text); + } + } +}; - nssv_constexpr explicit not_in_view(basic_string_view v) : v(v) {} +} // namespace inja - nssv_constexpr bool operator()(CharT c) const { return npos == v.find_first_of(c); } - }; +#endif // INCLUDE_INJA_TOKEN_HPP_ - nssv_constexpr size_type to_pos(const_iterator it) const { return it == cend() ? npos : size_type(it - cbegin()); } +// #include "utils.hpp" - nssv_constexpr size_type to_pos(const_reverse_iterator it) const { - return it == crend() ? npos : size_type(crend() - it - 1); - } - nssv_constexpr const_reference data_at(size_type pos) const { -#if nssv_BETWEEN(nssv_COMPILER_GNUC_VERSION, 1, 500) - return data_[pos]; -#else - return assert(pos < size()), data_[pos]; -#endif - } +namespace inja { - private: - const_pointer data_; - size_type size_; +/*! + * \brief Class for lexing an inja Template. + */ +class Lexer { + enum class State { + Text, + ExpressionStart, + ExpressionStartForceLstrip, + ExpressionBody, + LineStart, + LineBody, + StatementStart, + StatementStartNoLstrip, + StatementStartForceLstrip, + StatementBody, + CommentStart, + CommentStartForceLstrip, + CommentBody, + }; - public: -#if nssv_CONFIG_CONVERSION_STD_STRING_CLASS_METHODS + enum class MinusState { + Operator, + Number, + }; - template - basic_string_view(std::basic_string const &s) nssv_noexcept : data_(s.data()), - size_(s.size()) {} + const LexerConfig& config; -#if nssv_HAVE_EXPLICIT_CONVERSION + State state; + MinusState minus_state; + std::string_view m_in; + size_t tok_start; + size_t pos; - template explicit operator std::basic_string() const { - return to_string(Allocator()); + Token scan_body(std::string_view close, Token::Kind closeKind, std::string_view close_trim = std::string_view(), bool trim = false) { + again: + // skip whitespace (except for \n as it might be a close) + if (tok_start >= m_in.size()) { + return make_token(Token::Kind::Eof); } - -#endif // nssv_HAVE_EXPLICIT_CONVERSION - -#if nssv_CPP11_OR_GREATER - - template > - std::basic_string to_string(Allocator const &a = Allocator()) const { - return std::basic_string(begin(), end(), a); + const char ch = m_in[tok_start]; + if (ch == ' ' || ch == '\t' || ch == '\r') { + tok_start += 1; + goto again; } -#else + // check for close + if (!close_trim.empty() && inja::string_view::starts_with(m_in.substr(tok_start), close_trim)) { + state = State::Text; + pos = tok_start + close_trim.size(); + const Token tok = make_token(closeKind); + skip_whitespaces_and_newlines(); + return tok; + } - std::basic_string to_string() const { return std::basic_string(begin(), end()); } + if (inja::string_view::starts_with(m_in.substr(tok_start), close)) { + state = State::Text; + pos = tok_start + close.size(); + const Token tok = make_token(closeKind); + if (trim) { + skip_whitespaces_and_first_newline(); + } + return tok; + } - template std::basic_string to_string(Allocator const &a) const { - return std::basic_string(begin(), end(), a); + // skip \n + if (ch == '\n') { + tok_start += 1; + goto again; } -#endif // nssv_CPP11_OR_GREATER + pos = tok_start + 1; + if (std::isalpha(ch)) { + minus_state = MinusState::Operator; + return scan_id(); + } -#endif // nssv_CONFIG_CONVERSION_STD_STRING_CLASS_METHODS - }; - - // - // Non-member functions: - // - - // 24.4.3 Non-member comparison functions: - // lexicographically compare two string views (function template): - - template - nssv_constexpr bool operator==(basic_string_view lhs, - basic_string_view rhs) nssv_noexcept { - return lhs.compare(rhs) == 0; - } - - template - nssv_constexpr bool operator!=(basic_string_view lhs, - basic_string_view rhs) nssv_noexcept { - return lhs.compare(rhs) != 0; - } - - template - nssv_constexpr bool operator<(basic_string_view lhs, - basic_string_view rhs) nssv_noexcept { - return lhs.compare(rhs) < 0; - } - - template - nssv_constexpr bool operator<=(basic_string_view lhs, - basic_string_view rhs) nssv_noexcept { - return lhs.compare(rhs) <= 0; - } - - template - nssv_constexpr bool operator>(basic_string_view lhs, - basic_string_view rhs) nssv_noexcept { - return lhs.compare(rhs) > 0; - } - - template - nssv_constexpr bool operator>=(basic_string_view lhs, - basic_string_view rhs) nssv_noexcept { - return lhs.compare(rhs) >= 0; - } - - // Let S be basic_string_view, and sv be an instance of S. - // Implementations shall provide sufficient additional overloads marked - // constexpr and noexcept so that an object t with an implicit conversion - // to S can be compared according to Table 67. - -#if !nssv_CPP11_OR_GREATER || nssv_BETWEEN(nssv_COMPILER_MSVC_VERSION, 100, 141) - - // accomodate for older compilers: - - // == - - template - nssv_constexpr bool operator==(basic_string_view lhs, char const *rhs) nssv_noexcept { - return lhs.compare(rhs) == 0; - } - - template - nssv_constexpr bool operator==(char const *lhs, basic_string_view rhs) nssv_noexcept { - return rhs.compare(lhs) == 0; - } - - template - nssv_constexpr bool operator==(basic_string_view lhs, - std::basic_string rhs) nssv_noexcept { - return lhs.size() == rhs.size() && lhs.compare(rhs) == 0; - } - - template - nssv_constexpr bool operator==(std::basic_string rhs, - basic_string_view lhs) nssv_noexcept { - return lhs.size() == rhs.size() && lhs.compare(rhs) == 0; - } - - // != - - template - nssv_constexpr bool operator!=(basic_string_view lhs, char const *rhs) nssv_noexcept { - return lhs.compare(rhs) != 0; - } - - template - nssv_constexpr bool operator!=(char const *lhs, basic_string_view rhs) nssv_noexcept { - return rhs.compare(lhs) != 0; - } - - template - nssv_constexpr bool operator!=(basic_string_view lhs, - std::basic_string rhs) nssv_noexcept { - return lhs.size() != rhs.size() && lhs.compare(rhs) != 0; - } - - template - nssv_constexpr bool operator!=(std::basic_string rhs, - basic_string_view lhs) nssv_noexcept { - return lhs.size() != rhs.size() || rhs.compare(lhs) != 0; - } - - // < - - template - nssv_constexpr bool operator<(basic_string_view lhs, char const *rhs) nssv_noexcept { - return lhs.compare(rhs) < 0; - } - - template - nssv_constexpr bool operator<(char const *lhs, basic_string_view rhs) nssv_noexcept { - return rhs.compare(lhs) > 0; - } - - template - nssv_constexpr bool operator<(basic_string_view lhs, - std::basic_string rhs) nssv_noexcept { - return lhs.compare(rhs) < 0; - } - - template - nssv_constexpr bool operator<(std::basic_string rhs, - basic_string_view lhs) nssv_noexcept { - return rhs.compare(lhs) > 0; - } - - // <= - - template - nssv_constexpr bool operator<=(basic_string_view lhs, char const *rhs) nssv_noexcept { - return lhs.compare(rhs) <= 0; - } - - template - nssv_constexpr bool operator<=(char const *lhs, basic_string_view rhs) nssv_noexcept { - return rhs.compare(lhs) >= 0; - } - - template - nssv_constexpr bool operator<=(basic_string_view lhs, - std::basic_string rhs) nssv_noexcept { - return lhs.compare(rhs) <= 0; - } - - template - nssv_constexpr bool operator<=(std::basic_string rhs, - basic_string_view lhs) nssv_noexcept { - return rhs.compare(lhs) >= 0; - } - - // > - - template - nssv_constexpr bool operator>(basic_string_view lhs, char const *rhs) nssv_noexcept { - return lhs.compare(rhs) > 0; - } - - template - nssv_constexpr bool operator>(char const *lhs, basic_string_view rhs) nssv_noexcept { - return rhs.compare(lhs) < 0; - } - - template - nssv_constexpr bool operator>(basic_string_view lhs, - std::basic_string rhs) nssv_noexcept { - return lhs.compare(rhs) > 0; - } - - template - nssv_constexpr bool operator>(std::basic_string rhs, - basic_string_view lhs) nssv_noexcept { - return rhs.compare(lhs) < 0; - } - - // >= - - template - nssv_constexpr bool operator>=(basic_string_view lhs, char const *rhs) nssv_noexcept { - return lhs.compare(rhs) >= 0; - } - - template - nssv_constexpr bool operator>=(char const *lhs, basic_string_view rhs) nssv_noexcept { - return rhs.compare(lhs) <= 0; - } - - template - nssv_constexpr bool operator>=(basic_string_view lhs, - std::basic_string rhs) nssv_noexcept { - return lhs.compare(rhs) >= 0; - } - - template - nssv_constexpr bool operator>=(std::basic_string rhs, - basic_string_view lhs) nssv_noexcept { - return rhs.compare(lhs) <= 0; - } - -#else // newer compilers: - -#define nssv_BASIC_STRING_VIEW_I(T, U) typename std::decay>::type - -#if nssv_BETWEEN(nssv_COMPILER_MSVC_VERSION, 140, 150) -#define nssv_MSVC_ORDER(x) , int = x -#else -#define nssv_MSVC_ORDER(x) /*, int=x*/ -#endif - - // == - - template - nssv_constexpr bool operator==(basic_string_view lhs, - nssv_BASIC_STRING_VIEW_I(CharT, Traits) rhs) nssv_noexcept { - return lhs.compare(rhs) == 0; - } - - template - nssv_constexpr bool operator==(nssv_BASIC_STRING_VIEW_I(CharT, Traits) lhs, - basic_string_view rhs) nssv_noexcept { - return lhs.size() == rhs.size() && lhs.compare(rhs) == 0; - } - - // != - - template - nssv_constexpr bool operator!=(basic_string_view lhs, - nssv_BASIC_STRING_VIEW_I(CharT, Traits) rhs) nssv_noexcept { - return lhs.size() != rhs.size() || lhs.compare(rhs) != 0; - } - - template - nssv_constexpr bool operator!=(nssv_BASIC_STRING_VIEW_I(CharT, Traits) lhs, - basic_string_view rhs) nssv_noexcept { - return lhs.compare(rhs) != 0; - } - - // < - - template - nssv_constexpr bool operator<(basic_string_view lhs, - nssv_BASIC_STRING_VIEW_I(CharT, Traits) rhs) nssv_noexcept { - return lhs.compare(rhs) < 0; - } - - template - nssv_constexpr bool operator<(nssv_BASIC_STRING_VIEW_I(CharT, Traits) lhs, - basic_string_view rhs) nssv_noexcept { - return lhs.compare(rhs) < 0; - } - - // <= - - template - nssv_constexpr bool operator<=(basic_string_view lhs, - nssv_BASIC_STRING_VIEW_I(CharT, Traits) rhs) nssv_noexcept { - return lhs.compare(rhs) <= 0; - } - - template - nssv_constexpr bool operator<=(nssv_BASIC_STRING_VIEW_I(CharT, Traits) lhs, - basic_string_view rhs) nssv_noexcept { - return lhs.compare(rhs) <= 0; - } - - // > - - template - nssv_constexpr bool operator>(basic_string_view lhs, - nssv_BASIC_STRING_VIEW_I(CharT, Traits) rhs) nssv_noexcept { - return lhs.compare(rhs) > 0; - } - - template - nssv_constexpr bool operator>(nssv_BASIC_STRING_VIEW_I(CharT, Traits) lhs, - basic_string_view rhs) nssv_noexcept { - return lhs.compare(rhs) > 0; - } - - // >= - - template - nssv_constexpr bool operator>=(basic_string_view lhs, - nssv_BASIC_STRING_VIEW_I(CharT, Traits) rhs) nssv_noexcept { - return lhs.compare(rhs) >= 0; - } - - template - nssv_constexpr bool operator>=(nssv_BASIC_STRING_VIEW_I(CharT, Traits) lhs, - basic_string_view rhs) nssv_noexcept { - return lhs.compare(rhs) >= 0; - } - -#undef nssv_MSVC_ORDER -#undef nssv_BASIC_STRING_VIEW_I - -#endif // compiler-dependent approach to comparisons - - // 24.4.4 Inserters and extractors: - - namespace detail { - - template void write_padding(Stream &os, std::streamsize n) { - for (std::streamsize i = 0; i < n; ++i) - os.rdbuf()->sputc(os.fill()); - } - - template Stream &write_to_stream(Stream &os, View const &sv) { - typename Stream::sentry sentry(os); - - if (!os) - return os; - - const std::streamsize length = static_cast(sv.length()); - - // Whether, and how, to pad: - const bool pad = (length < os.width()); - const bool left_pad = pad && (os.flags() & std::ios_base::adjustfield) == std::ios_base::right; - - if (left_pad) - write_padding(os, os.width() - length); - - // Write span characters: - os.rdbuf()->sputn(sv.begin(), length); - - if (pad && !left_pad) - write_padding(os, os.width() - length); - - // Reset output stream width: - os.width(0); - - return os; - } - - } // namespace detail - - template - std::basic_ostream &operator<<(std::basic_ostream &os, - basic_string_view sv) { - return detail::write_to_stream(os, sv); - } - - // Several typedefs for common character types are provided: - - typedef basic_string_view string_view; - typedef basic_string_view wstring_view; -#if nssv_HAVE_WCHAR16_T - typedef basic_string_view u16string_view; - typedef basic_string_view u32string_view; -#endif - - } // namespace sv_lite -} // namespace nonstd::sv_lite - -// -// 24.4.6 Suffix for basic_string_view literals: -// - -#if nssv_HAVE_USER_DEFINED_LITERALS - -namespace nonstd { -nssv_inline_ns namespace literals { - nssv_inline_ns namespace string_view_literals { - -#if nssv_CONFIG_STD_SV_OPERATOR && nssv_HAVE_STD_DEFINED_LITERALS - - nssv_constexpr nonstd::sv_lite::string_view operator"" sv(const char *str, size_t len) nssv_noexcept // (1) - { - return nonstd::sv_lite::string_view {str, len}; - } - - nssv_constexpr nonstd::sv_lite::u16string_view operator"" sv(const char16_t *str, size_t len) nssv_noexcept // (2) - { - return nonstd::sv_lite::u16string_view {str, len}; - } - - nssv_constexpr nonstd::sv_lite::u32string_view operator"" sv(const char32_t *str, size_t len) nssv_noexcept // (3) - { - return nonstd::sv_lite::u32string_view {str, len}; - } - - nssv_constexpr nonstd::sv_lite::wstring_view operator"" sv(const wchar_t *str, size_t len) nssv_noexcept // (4) - { - return nonstd::sv_lite::wstring_view {str, len}; - } - -#endif // nssv_CONFIG_STD_SV_OPERATOR && nssv_HAVE_STD_DEFINED_LITERALS - -#if nssv_CONFIG_USR_SV_OPERATOR - - nssv_constexpr nonstd::sv_lite::string_view operator"" _sv(const char *str, size_t len) nssv_noexcept // (1) - { - return nonstd::sv_lite::string_view {str, len}; - } - - nssv_constexpr nonstd::sv_lite::u16string_view operator"" _sv(const char16_t *str, size_t len) nssv_noexcept // (2) - { - return nonstd::sv_lite::u16string_view {str, len}; - } - - nssv_constexpr nonstd::sv_lite::u32string_view operator"" _sv(const char32_t *str, size_t len) nssv_noexcept // (3) - { - return nonstd::sv_lite::u32string_view {str, len}; - } - - nssv_constexpr nonstd::sv_lite::wstring_view operator"" _sv(const wchar_t *str, size_t len) nssv_noexcept // (4) - { - return nonstd::sv_lite::wstring_view {str, len}; - } - -#endif // nssv_CONFIG_USR_SV_OPERATOR - } -} -} // namespace nonstd - -#endif - -// -// Extensions for std::string: -// - -#if nssv_CONFIG_CONVERSION_STD_STRING_FREE_FUNCTIONS - -namespace nonstd { -namespace sv_lite { - -// Exclude MSVC 14 (19.00): it yields ambiguous to_string(): - -#if nssv_CPP11_OR_GREATER && nssv_COMPILER_MSVC_VERSION != 140 - -template > -std::basic_string to_string(basic_string_view v, - Allocator const &a = Allocator()) { - return std::basic_string(v.begin(), v.end(), a); -} - -#else - -template std::basic_string to_string(basic_string_view v) { - return std::basic_string(v.begin(), v.end()); -} - -template -std::basic_string to_string(basic_string_view v, Allocator const &a) { - return std::basic_string(v.begin(), v.end(), a); -} - -#endif // nssv_CPP11_OR_GREATER - -template -basic_string_view to_string_view(std::basic_string const &s) { - return basic_string_view(s.data(), s.size()); -} - -} // namespace sv_lite -} // namespace nonstd - -#endif // nssv_CONFIG_CONVERSION_STD_STRING_FREE_FUNCTIONS - -// -// make types and algorithms available in namespace nonstd: -// - -namespace nonstd { - -using sv_lite::basic_string_view; -using sv_lite::string_view; -using sv_lite::wstring_view; - -#if nssv_HAVE_WCHAR16_T -using sv_lite::u16string_view; -#endif -#if nssv_HAVE_WCHAR32_T -using sv_lite::u32string_view; -#endif - -// literal "sv" - -using sv_lite::operator==; -using sv_lite::operator!=; -using sv_lite::operator<; -using sv_lite::operator<=; -using sv_lite::operator>; -using sv_lite::operator>=; - -using sv_lite::operator<<; - -#if nssv_CONFIG_CONVERSION_STD_STRING_FREE_FUNCTIONS -using sv_lite::to_string; -using sv_lite::to_string_view; -#endif - -} // namespace nonstd - -// 24.4.5 Hash support (C++11): - -// Note: The hash value of a string view object is equal to the hash value of -// the corresponding string object. - -#if nssv_HAVE_STD_HASH - -#include - -namespace std { - -template <> struct hash { -public: - std::size_t operator()(nonstd::string_view v) const nssv_noexcept { - return std::hash()(std::string(v.data(), v.size())); - } -}; - -template <> struct hash { -public: - std::size_t operator()(nonstd::wstring_view v) const nssv_noexcept { - return std::hash()(std::wstring(v.data(), v.size())); - } -}; - -template <> struct hash { -public: - std::size_t operator()(nonstd::u16string_view v) const nssv_noexcept { - return std::hash()(std::u16string(v.data(), v.size())); - } -}; - -template <> struct hash { -public: - std::size_t operator()(nonstd::u32string_view v) const nssv_noexcept { - return std::hash()(std::u32string(v.data(), v.size())); - } -}; - -} // namespace std - -#endif // nssv_HAVE_STD_HASH - -nssv_RESTORE_WARNINGS() - -#endif // nssv_HAVE_STD_STRING_VIEW -#endif // NONSTD_SV_LITE_H_INCLUDED - - -namespace inja { - -/*! - * \brief Class for lexer configuration. - */ -struct LexerConfig { - std::string statement_open {"{%"}; - std::string statement_open_no_lstrip {"{%+"}; - std::string statement_open_force_lstrip {"{%-"}; - std::string statement_close {"%}"}; - std::string statement_close_force_rstrip {"-%}"}; - std::string line_statement {"##"}; - std::string expression_open {"{{"}; - std::string expression_open_force_lstrip {"{{-"}; - std::string expression_close {"}}"}; - std::string expression_close_force_rstrip {"-}}"}; - std::string comment_open {"{#"}; - std::string comment_close {"#}"}; - std::string open_chars {"#{"}; - - bool trim_blocks {false}; - bool lstrip_blocks {false}; - - void update_open_chars() { - open_chars = ""; - if (open_chars.find(line_statement[0]) == std::string::npos) { - open_chars += line_statement[0]; - } - if (open_chars.find(statement_open[0]) == std::string::npos) { - open_chars += statement_open[0]; - } - if (open_chars.find(statement_open_no_lstrip[0]) == std::string::npos) { - open_chars += statement_open_no_lstrip[0]; - } - if (open_chars.find(statement_open_force_lstrip[0]) == std::string::npos) { - open_chars += statement_open_force_lstrip[0]; - } - if (open_chars.find(expression_open[0]) == std::string::npos) { - open_chars += expression_open[0]; - } - if (open_chars.find(expression_open_force_lstrip[0]) == std::string::npos) { - open_chars += expression_open_force_lstrip[0]; - } - if (open_chars.find(comment_open[0]) == std::string::npos) { - open_chars += comment_open[0]; - } - } -}; - -/*! - * \brief Class for parser configuration. - */ -struct ParserConfig { - bool search_included_templates_in_files {true}; -}; - -/*! - * \brief Class for render configuration. - */ -struct RenderConfig { - bool throw_at_missing_includes {true}; -}; - -} // namespace inja - -#endif // INCLUDE_INJA_CONFIG_HPP_ - -// #include "function_storage.hpp" -// Copyright (c) 2020 Pantor. All rights reserved. - -#ifndef INCLUDE_INJA_FUNCTION_STORAGE_HPP_ -#define INCLUDE_INJA_FUNCTION_STORAGE_HPP_ - -#include - -// #include "string_view.hpp" - - -namespace inja { - -using json = nlohmann::json; - -using Arguments = std::vector; -using CallbackFunction = std::function; -using VoidCallbackFunction = std::function; - -/*! - * \brief Class for builtin functions and user-defined callbacks. - */ -class FunctionStorage { -public: - enum class Operation { - Not, - And, - Or, - In, - Equal, - NotEqual, - Greater, - GreaterEqual, - Less, - LessEqual, - Add, - Subtract, - Multiplication, - Division, - Power, - Modulo, - AtId, - At, - Default, - DivisibleBy, - Even, - Exists, - ExistsInObject, - First, - Float, - Int, - IsArray, - IsBoolean, - IsFloat, - IsInteger, - IsNumber, - IsObject, - IsString, - Last, - Length, - Lower, - Max, - Min, - Odd, - Range, - Round, - Sort, - Upper, - Callback, - ParenLeft, - ParenRight, - None, - }; - - struct FunctionData { - explicit FunctionData(const Operation &op, const CallbackFunction &cb = CallbackFunction{}) : operation(op), callback(cb) {} - const Operation operation; - const CallbackFunction callback; - }; - -private: - const int VARIADIC {-1}; - - std::map, FunctionData> function_storage = { - {std::make_pair("at", 2), FunctionData { Operation::At }}, - {std::make_pair("default", 2), FunctionData { Operation::Default }}, - {std::make_pair("divisibleBy", 2), FunctionData { Operation::DivisibleBy }}, - {std::make_pair("even", 1), FunctionData { Operation::Even }}, - {std::make_pair("exists", 1), FunctionData { Operation::Exists }}, - {std::make_pair("existsIn", 2), FunctionData { Operation::ExistsInObject }}, - {std::make_pair("first", 1), FunctionData { Operation::First }}, - {std::make_pair("float", 1), FunctionData { Operation::Float }}, - {std::make_pair("int", 1), FunctionData { Operation::Int }}, - {std::make_pair("isArray", 1), FunctionData { Operation::IsArray }}, - {std::make_pair("isBoolean", 1), FunctionData { Operation::IsBoolean }}, - {std::make_pair("isFloat", 1), FunctionData { Operation::IsFloat }}, - {std::make_pair("isInteger", 1), FunctionData { Operation::IsInteger }}, - {std::make_pair("isNumber", 1), FunctionData { Operation::IsNumber }}, - {std::make_pair("isObject", 1), FunctionData { Operation::IsObject }}, - {std::make_pair("isString", 1), FunctionData { Operation::IsString }}, - {std::make_pair("last", 1), FunctionData { Operation::Last }}, - {std::make_pair("length", 1), FunctionData { Operation::Length }}, - {std::make_pair("lower", 1), FunctionData { Operation::Lower }}, - {std::make_pair("max", 1), FunctionData { Operation::Max }}, - {std::make_pair("min", 1), FunctionData { Operation::Min }}, - {std::make_pair("odd", 1), FunctionData { Operation::Odd }}, - {std::make_pair("range", 1), FunctionData { Operation::Range }}, - {std::make_pair("round", 2), FunctionData { Operation::Round }}, - {std::make_pair("sort", 1), FunctionData { Operation::Sort }}, - {std::make_pair("upper", 1), FunctionData { Operation::Upper }}, - }; - -public: - void add_builtin(nonstd::string_view name, int num_args, Operation op) { - function_storage.emplace(std::make_pair(static_cast(name), num_args), FunctionData { op }); - } - - void add_callback(nonstd::string_view name, int num_args, const CallbackFunction &callback) { - function_storage.emplace(std::make_pair(static_cast(name), num_args), FunctionData { Operation::Callback, callback }); - } - - FunctionData find_function(nonstd::string_view name, int num_args) const { - auto it = function_storage.find(std::make_pair(static_cast(name), num_args)); - if (it != function_storage.end()) { - return it->second; - - // Find variadic function - } else if (num_args > 0) { - it = function_storage.find(std::make_pair(static_cast(name), VARIADIC)); - if (it != function_storage.end()) { - return it->second; - } - } - - return FunctionData { Operation::None }; - } -}; - -} // namespace inja - -#endif // INCLUDE_INJA_FUNCTION_STORAGE_HPP_ - -// #include "parser.hpp" -// Copyright (c) 2020 Pantor. All rights reserved. - -#ifndef INCLUDE_INJA_PARSER_HPP_ -#define INCLUDE_INJA_PARSER_HPP_ - -#include -#include -#include -#include -#include -#include - -// #include "config.hpp" - -// #include "exceptions.hpp" -// Copyright (c) 2020 Pantor. All rights reserved. - -#ifndef INCLUDE_INJA_EXCEPTIONS_HPP_ -#define INCLUDE_INJA_EXCEPTIONS_HPP_ - -#include -#include - -namespace inja { - -struct SourceLocation { - size_t line; - size_t column; -}; - -struct InjaError : public std::runtime_error { - const std::string type; - const std::string message; - - const SourceLocation location; - - explicit InjaError(const std::string &type, const std::string &message) - : std::runtime_error("[inja.exception." + type + "] " + message), type(type), message(message), location({0, 0}) {} - - explicit InjaError(const std::string &type, const std::string &message, SourceLocation location) - : std::runtime_error("[inja.exception." + type + "] (at " + std::to_string(location.line) + ":" + - std::to_string(location.column) + ") " + message), - type(type), message(message), location(location) {} -}; - -struct ParserError : public InjaError { - explicit ParserError(const std::string &message, SourceLocation location) : InjaError("parser_error", message, location) {} -}; - -struct RenderError : public InjaError { - explicit RenderError(const std::string &message, SourceLocation location) : InjaError("render_error", message, location) {} -}; - -struct FileError : public InjaError { - explicit FileError(const std::string &message) : InjaError("file_error", message) {} - explicit FileError(const std::string &message, SourceLocation location) : InjaError("file_error", message, location) {} -}; - -struct JsonError : public InjaError { - explicit JsonError(const std::string &message, SourceLocation location) : InjaError("json_error", message, location) {} -}; - -} // namespace inja - -#endif // INCLUDE_INJA_EXCEPTIONS_HPP_ - -// #include "function_storage.hpp" - -// #include "lexer.hpp" -// Copyright (c) 2020 Pantor. All rights reserved. - -#ifndef INCLUDE_INJA_LEXER_HPP_ -#define INCLUDE_INJA_LEXER_HPP_ - -#include -#include - -// #include "config.hpp" - -// #include "token.hpp" -// Copyright (c) 2020 Pantor. All rights reserved. - -#ifndef INCLUDE_INJA_TOKEN_HPP_ -#define INCLUDE_INJA_TOKEN_HPP_ - -#include - -// #include "string_view.hpp" - - -namespace inja { - -/*! - * \brief Helper-class for the inja Lexer. - */ -struct Token { - enum class Kind { - Text, - ExpressionOpen, // {{ - ExpressionClose, // }} - LineStatementOpen, // ## - LineStatementClose, // \n - StatementOpen, // {% - StatementClose, // %} - CommentOpen, // {# - CommentClose, // #} - Id, // this, this.foo - Number, // 1, 2, -1, 5.2, -5.3 - String, // "this" - Plus, // + - Minus, // - - Times, // * - Slash, // / - Percent, // % - Power, // ^ - Comma, // , - Dot, // . - Colon, // : - LeftParen, // ( - RightParen, // ) - LeftBracket, // [ - RightBracket, // ] - LeftBrace, // { - RightBrace, // } - Equal, // == - NotEqual, // != - GreaterThan, // > - GreaterEqual, // >= - LessThan, // < - LessEqual, // <= - Unknown, - Eof, - }; - - Kind kind {Kind::Unknown}; - nonstd::string_view text; - - explicit constexpr Token() = default; - explicit constexpr Token(Kind kind, nonstd::string_view text) : kind(kind), text(text) {} - - std::string describe() const { - switch (kind) { - case Kind::Text: - return ""; - case Kind::LineStatementClose: - return ""; - case Kind::Eof: - return ""; - default: - return static_cast(text); - } - } -}; - -} // namespace inja - -#endif // INCLUDE_INJA_TOKEN_HPP_ - -// #include "utils.hpp" -// Copyright (c) 2020 Pantor. All rights reserved. - -#ifndef INCLUDE_INJA_UTILS_HPP_ -#define INCLUDE_INJA_UTILS_HPP_ - -#include -#include -#include -#include - -// #include "exceptions.hpp" - -// #include "string_view.hpp" - - -namespace inja { - -inline void open_file_or_throw(const std::string &path, std::ifstream &file) { - file.exceptions(std::ifstream::failbit | std::ifstream::badbit); - try { - file.open(path); - } catch (const std::ios_base::failure & /*e*/) { - INJA_THROW(FileError("failed accessing file at '" + path + "'")); - } -} - -namespace string_view { -inline nonstd::string_view slice(nonstd::string_view view, size_t start, size_t end) { - start = std::min(start, view.size()); - end = std::min(std::max(start, end), view.size()); - return view.substr(start, end - start); -} - -inline std::pair split(nonstd::string_view view, char Separator) { - size_t idx = view.find(Separator); - if (idx == nonstd::string_view::npos) { - return std::make_pair(view, nonstd::string_view()); - } - return std::make_pair(slice(view, 0, idx), slice(view, idx + 1, nonstd::string_view::npos)); -} - -inline bool starts_with(nonstd::string_view view, nonstd::string_view prefix) { - return (view.size() >= prefix.size() && view.compare(0, prefix.size(), prefix) == 0); -} -} // namespace string_view - -inline SourceLocation get_source_location(nonstd::string_view content, size_t pos) { - // Get line and offset position (starts at 1:1) - auto sliced = string_view::slice(content, 0, pos); - std::size_t last_newline = sliced.rfind("\n"); - - if (last_newline == nonstd::string_view::npos) { - return {1, sliced.length() + 1}; - } - - // Count newlines - size_t count_lines = 0; - size_t search_start = 0; - while (search_start <= sliced.size()) { - search_start = sliced.find("\n", search_start) + 1; - if (search_start == 0) { - break; - } - count_lines += 1; - } - - return {count_lines + 1, sliced.length() - last_newline}; -} - -} // namespace inja - -#endif // INCLUDE_INJA_UTILS_HPP_ - - -namespace inja { - -/*! - * \brief Class for lexing an inja Template. - */ -class Lexer { - enum class State { - Text, - ExpressionStart, - ExpressionStartForceLstrip, - ExpressionBody, - LineStart, - LineBody, - StatementStart, - StatementStartNoLstrip, - StatementStartForceLstrip, - StatementBody, - CommentStart, - CommentBody, - }; - - enum class MinusState { - Operator, - Number, - }; - - const LexerConfig &config; - - State state; - MinusState minus_state; - nonstd::string_view m_in; - size_t tok_start; - size_t pos; - - - Token scan_body(nonstd::string_view close, Token::Kind closeKind, nonstd::string_view close_trim = nonstd::string_view(), bool trim = false) { - again: - // skip whitespace (except for \n as it might be a close) - if (tok_start >= m_in.size()) { - return make_token(Token::Kind::Eof); - } - char ch = m_in[tok_start]; - if (ch == ' ' || ch == '\t' || ch == '\r') { - tok_start += 1; - goto again; - } - - // check for close - if (!close_trim.empty() && inja::string_view::starts_with(m_in.substr(tok_start), close_trim)) { - state = State::Text; - pos = tok_start + close_trim.size(); - Token tok = make_token(closeKind); - skip_whitespaces_and_newlines(); - return tok; - } - - if (inja::string_view::starts_with(m_in.substr(tok_start), close)) { - state = State::Text; - pos = tok_start + close.size(); - Token tok = make_token(closeKind); - if (trim) { - skip_whitespaces_and_first_newline(); - } - return tok; - } - - // skip \n - if (ch == '\n') { - tok_start += 1; - goto again; - } - - pos = tok_start + 1; - if (std::isalpha(ch)) { - minus_state = MinusState::Operator; - return scan_id(); - } - - MinusState current_minus_state = minus_state; - if (minus_state == MinusState::Operator) { - minus_state = MinusState::Number; - } - - switch (ch) { - case '+': - return make_token(Token::Kind::Plus); - case '-': - if (current_minus_state == MinusState::Operator) { - return make_token(Token::Kind::Minus); - } - return scan_number(); - case '*': - return make_token(Token::Kind::Times); - case '/': - return make_token(Token::Kind::Slash); - case '^': - return make_token(Token::Kind::Power); - case '%': - return make_token(Token::Kind::Percent); - case '.': - return make_token(Token::Kind::Dot); - case ',': - return make_token(Token::Kind::Comma); - case ':': - return make_token(Token::Kind::Colon); - case '(': - return make_token(Token::Kind::LeftParen); - case ')': - minus_state = MinusState::Operator; - return make_token(Token::Kind::RightParen); - case '[': - return make_token(Token::Kind::LeftBracket); - case ']': - minus_state = MinusState::Operator; - return make_token(Token::Kind::RightBracket); - case '{': - return make_token(Token::Kind::LeftBrace); - case '}': - minus_state = MinusState::Operator; - return make_token(Token::Kind::RightBrace); - case '>': - if (pos < m_in.size() && m_in[pos] == '=') { - pos += 1; - return make_token(Token::Kind::GreaterEqual); - } - return make_token(Token::Kind::GreaterThan); - case '<': - if (pos < m_in.size() && m_in[pos] == '=') { - pos += 1; - return make_token(Token::Kind::LessEqual); - } - return make_token(Token::Kind::LessThan); - case '=': - if (pos < m_in.size() && m_in[pos] == '=') { - pos += 1; - return make_token(Token::Kind::Equal); - } - return make_token(Token::Kind::Unknown); - case '!': - if (pos < m_in.size() && m_in[pos] == '=') { - pos += 1; - return make_token(Token::Kind::NotEqual); - } - return make_token(Token::Kind::Unknown); - case '\"': - return scan_string(); - case '0': - case '1': - case '2': - case '3': - case '4': - case '5': - case '6': - case '7': - case '8': - case '9': - minus_state = MinusState::Operator; - return scan_number(); - case '_': - case '@': - case '$': - minus_state = MinusState::Operator; - return scan_id(); - default: - return make_token(Token::Kind::Unknown); - } - } - - Token scan_id() { - for (;;) { - if (pos >= m_in.size()) { - break; - } - char ch = m_in[pos]; - if (!std::isalnum(ch) && ch != '.' && ch != '/' && ch != '_' && ch != '-') { - break; - } - pos += 1; - } - return make_token(Token::Kind::Id); - } - - Token scan_number() { - for (;;) { - if (pos >= m_in.size()) { - break; - } - char ch = m_in[pos]; - // be very permissive in lexer (we'll catch errors when conversion happens) - if (!std::isdigit(ch) && ch != '.' && ch != 'e' && ch != 'E' && ch != '+' && ch != '-') { - break; - } - pos += 1; - } - return make_token(Token::Kind::Number); - } - - Token scan_string() { - bool escape {false}; - for (;;) { - if (pos >= m_in.size()) { - break; - } - char ch = m_in[pos++]; - if (ch == '\\') { - escape = true; - } else if (!escape && ch == m_in[tok_start]) { - break; - } else { - escape = false; - } - } - return make_token(Token::Kind::String); - } - - Token make_token(Token::Kind kind) const { return Token(kind, string_view::slice(m_in, tok_start, pos)); } - - void skip_whitespaces_and_newlines() { - if (pos < m_in.size()) { - while (pos < m_in.size() && (m_in[pos] == ' ' || m_in[pos] == '\t' || m_in[pos] == '\n' || m_in[pos] == '\r')) { - pos += 1; - } - } - } - - void skip_whitespaces_and_first_newline() { - if (pos < m_in.size()) { - while (pos < m_in.size() && (m_in[pos] == ' ' || m_in[pos] == '\t')) { - pos += 1; - } - } - - if (pos < m_in.size()) { - char ch = m_in[pos]; - if (ch == '\n') { - pos += 1; - } else if (ch == '\r') { - pos += 1; - if (pos < m_in.size() && m_in[pos] == '\n') { - pos += 1; - } - } - } - } - - static nonstd::string_view clear_final_line_if_whitespace(nonstd::string_view text) { - nonstd::string_view result = text; - while (!result.empty()) { - char ch = result.back(); - if (ch == ' ' || ch == '\t') { - result.remove_suffix(1); - } else if (ch == '\n' || ch == '\r') { - break; - } else { - return text; - } - } - return result; - } - -public: - explicit Lexer(const LexerConfig &config) : config(config), state(State::Text), minus_state(MinusState::Number) {} - - SourceLocation current_position() const { - return get_source_location(m_in, tok_start); - } - - void start(nonstd::string_view input) { - m_in = input; - tok_start = 0; - pos = 0; - state = State::Text; - minus_state = MinusState::Number; - - // Consume byte order mark (BOM) for UTF-8 - if (inja::string_view::starts_with(m_in, "\xEF\xBB\xBF")) { - m_in = m_in.substr(3); - } - } - - Token scan() { - tok_start = pos; - - again: - if (tok_start >= m_in.size()) { - return make_token(Token::Kind::Eof); - } - - switch (state) { - default: - case State::Text: { - // fast-scan to first open character - size_t open_start = m_in.substr(pos).find_first_of(config.open_chars); - if (open_start == nonstd::string_view::npos) { - // didn't find open, return remaining text as text token - pos = m_in.size(); - return make_token(Token::Kind::Text); - } - pos += open_start; - - // try to match one of the opening sequences, and get the close - nonstd::string_view open_str = m_in.substr(pos); - bool must_lstrip = false; - if (inja::string_view::starts_with(open_str, config.expression_open)) { - if (inja::string_view::starts_with(open_str, config.expression_open_force_lstrip)) { - state = State::ExpressionStartForceLstrip; - must_lstrip = true; - } else { - state = State::ExpressionStart; - } - } else if (inja::string_view::starts_with(open_str, config.statement_open)) { - if (inja::string_view::starts_with(open_str, config.statement_open_no_lstrip)) { - state = State::StatementStartNoLstrip; - } else if (inja::string_view::starts_with(open_str, config.statement_open_force_lstrip )) { - state = State::StatementStartForceLstrip; - must_lstrip = true; - } else { - state = State::StatementStart; - must_lstrip = config.lstrip_blocks; - } - } else if (inja::string_view::starts_with(open_str, config.comment_open)) { - state = State::CommentStart; - must_lstrip = config.lstrip_blocks; - } else if ((pos == 0 || m_in[pos - 1] == '\n') && inja::string_view::starts_with(open_str, config.line_statement)) { - state = State::LineStart; - } else { - pos += 1; // wasn't actually an opening sequence - goto again; - } - - nonstd::string_view text = string_view::slice(m_in, tok_start, pos); - if (must_lstrip) { - text = clear_final_line_if_whitespace(text); - } - - if (text.empty()) { - goto again; // don't generate empty token - } - return Token(Token::Kind::Text, text); - } - case State::ExpressionStart: { - state = State::ExpressionBody; - pos += config.expression_open.size(); - return make_token(Token::Kind::ExpressionOpen); - } - case State::ExpressionStartForceLstrip: { - state = State::ExpressionBody; - pos += config.expression_open_force_lstrip.size(); - return make_token(Token::Kind::ExpressionOpen); - } - case State::LineStart: { - state = State::LineBody; - pos += config.line_statement.size(); - return make_token(Token::Kind::LineStatementOpen); - } - case State::StatementStart: { - state = State::StatementBody; - pos += config.statement_open.size(); - return make_token(Token::Kind::StatementOpen); - } - case State::StatementStartNoLstrip: { - state = State::StatementBody; - pos += config.statement_open_no_lstrip.size(); - return make_token(Token::Kind::StatementOpen); - } - case State::StatementStartForceLstrip: { - state = State::StatementBody; - pos += config.statement_open_force_lstrip.size(); - return make_token(Token::Kind::StatementOpen); - } - case State::CommentStart: { - state = State::CommentBody; - pos += config.comment_open.size(); - return make_token(Token::Kind::CommentOpen); - } - case State::ExpressionBody: - return scan_body(config.expression_close, Token::Kind::ExpressionClose, config.expression_close_force_rstrip); - case State::LineBody: - return scan_body("\n", Token::Kind::LineStatementClose); - case State::StatementBody: - return scan_body(config.statement_close, Token::Kind::StatementClose, config.statement_close_force_rstrip, config.trim_blocks); - case State::CommentBody: { - // fast-scan to comment close - size_t end = m_in.substr(pos).find(config.comment_close); - if (end == nonstd::string_view::npos) { - pos = m_in.size(); - return make_token(Token::Kind::Eof); - } - // return the entire comment in the close token - state = State::Text; - pos += end + config.comment_close.size(); - Token tok = make_token(Token::Kind::CommentClose); - if (config.trim_blocks) { - skip_whitespaces_and_first_newline(); - } - return tok; - } + const MinusState current_minus_state = minus_state; + if (minus_state == MinusState::Operator) { + minus_state = MinusState::Number; } - } - - const LexerConfig &get_config() const { - return config; - } -}; - -} // namespace inja - -#endif // INCLUDE_INJA_LEXER_HPP_ - -// #include "node.hpp" -// Copyright (c) 2020 Pantor. All rights reserved. - -#ifndef INCLUDE_INJA_NODE_HPP_ -#define INCLUDE_INJA_NODE_HPP_ - -#include -#include - -#include - -// #include "function_storage.hpp" - -// #include "string_view.hpp" - - - -namespace inja { - -class NodeVisitor; -class BlockNode; -class TextNode; -class ExpressionNode; -class LiteralNode; -class JsonNode; -class FunctionNode; -class ExpressionListNode; -class StatementNode; -class ForStatementNode; -class ForArrayStatementNode; -class ForObjectStatementNode; -class IfStatementNode; -class IncludeStatementNode; -class SetStatementNode; - - -class NodeVisitor { -public: - virtual void visit(const BlockNode& node) = 0; - virtual void visit(const TextNode& node) = 0; - virtual void visit(const ExpressionNode& node) = 0; - virtual void visit(const LiteralNode& node) = 0; - virtual void visit(const JsonNode& node) = 0; - virtual void visit(const FunctionNode& node) = 0; - virtual void visit(const ExpressionListNode& node) = 0; - virtual void visit(const StatementNode& node) = 0; - virtual void visit(const ForStatementNode& node) = 0; - virtual void visit(const ForArrayStatementNode& node) = 0; - virtual void visit(const ForObjectStatementNode& node) = 0; - virtual void visit(const IfStatementNode& node) = 0; - virtual void visit(const IncludeStatementNode& node) = 0; - virtual void visit(const SetStatementNode& node) = 0; -}; - -/*! - * \brief Base node class for the abstract syntax tree (AST). - */ -class AstNode { -public: - virtual void accept(NodeVisitor& v) const = 0; - - size_t pos; - - AstNode(size_t pos) : pos(pos) { } - virtual ~AstNode() { }; -}; - - -class BlockNode : public AstNode { -public: - std::vector> nodes; - - explicit BlockNode() : AstNode(0) {} - - void accept(NodeVisitor& v) const { - v.visit(*this); - } -}; - -class TextNode : public AstNode { -public: - const size_t length; - - explicit TextNode(size_t pos, size_t length): AstNode(pos), length(length) { } - - void accept(NodeVisitor& v) const { - v.visit(*this); - } -}; - -class ExpressionNode : public AstNode { -public: - explicit ExpressionNode(size_t pos) : AstNode(pos) {} - - void accept(NodeVisitor& v) const { - v.visit(*this); - } -}; - -class LiteralNode : public ExpressionNode { -public: - const nlohmann::json value; - - explicit LiteralNode(const nlohmann::json& value, size_t pos) : ExpressionNode(pos), value(value) { } - - void accept(NodeVisitor& v) const { - v.visit(*this); - } -}; - -class JsonNode : public ExpressionNode { -public: - const std::string name; - const json::json_pointer ptr; - - static std::string convert_dot_to_json_ptr(nonstd::string_view ptr_name) { - std::string result; - do { - nonstd::string_view part; - std::tie(part, ptr_name) = string_view::split(ptr_name, '.'); - result.push_back('/'); - result.append(part.begin(), part.end()); - } while (!ptr_name.empty()); - return result; - } - - explicit JsonNode(nonstd::string_view ptr_name, size_t pos) : ExpressionNode(pos), name(ptr_name), ptr(json::json_pointer(convert_dot_to_json_ptr(ptr_name))) { } - - void accept(NodeVisitor& v) const { - v.visit(*this); - } -}; - -class FunctionNode : public ExpressionNode { - using Op = FunctionStorage::Operation; - -public: - enum class Associativity { - Left, - Right, - }; - - unsigned int precedence; - Associativity associativity; - Op operation; - - std::string name; - int number_args; // Should also be negative -> -1 for unknown number - CallbackFunction callback; - - explicit FunctionNode(nonstd::string_view name, size_t pos) : ExpressionNode(pos), precedence(8), associativity(Associativity::Left), operation(Op::Callback), name(name), number_args(1) { } - explicit FunctionNode(Op operation, size_t pos) : ExpressionNode(pos), operation(operation), number_args(1) { - switch (operation) { - case Op::Not: { - precedence = 4; - associativity = Associativity::Left; - } break; - case Op::And: { - precedence = 1; - associativity = Associativity::Left; - } break; - case Op::Or: { - precedence = 1; - associativity = Associativity::Left; - } break; - case Op::In: { - precedence = 2; - associativity = Associativity::Left; - } break; - case Op::Equal: { - precedence = 2; - associativity = Associativity::Left; - } break; - case Op::NotEqual: { - precedence = 2; - associativity = Associativity::Left; - } break; - case Op::Greater: { - precedence = 2; - associativity = Associativity::Left; - } break; - case Op::GreaterEqual: { - precedence = 2; - associativity = Associativity::Left; - } break; - case Op::Less: { - precedence = 2; - associativity = Associativity::Left; - } break; - case Op::LessEqual: { - precedence = 2; - associativity = Associativity::Left; - } break; - case Op::Add: { - precedence = 3; - associativity = Associativity::Left; - } break; - case Op::Subtract: { - precedence = 3; - associativity = Associativity::Left; - } break; - case Op::Multiplication: { - precedence = 4; - associativity = Associativity::Left; - } break; - case Op::Division: { - precedence = 4; - associativity = Associativity::Left; - } break; - case Op::Power: { - precedence = 5; - associativity = Associativity::Right; - } break; - case Op::Modulo: { - precedence = 4; - associativity = Associativity::Left; - } break; - case Op::AtId: { - precedence = 8; - associativity = Associativity::Left; - } break; - default: { - precedence = 1; - associativity = Associativity::Left; + switch (ch) { + case '+': + return make_token(Token::Kind::Plus); + case '-': + if (current_minus_state == MinusState::Operator) { + return make_token(Token::Kind::Minus); + } + return scan_number(); + case '*': + return make_token(Token::Kind::Times); + case '/': + return make_token(Token::Kind::Slash); + case '^': + return make_token(Token::Kind::Power); + case '%': + return make_token(Token::Kind::Percent); + case '.': + return make_token(Token::Kind::Dot); + case ',': + return make_token(Token::Kind::Comma); + case ':': + return make_token(Token::Kind::Colon); + case '(': + return make_token(Token::Kind::LeftParen); + case ')': + minus_state = MinusState::Operator; + return make_token(Token::Kind::RightParen); + case '[': + return make_token(Token::Kind::LeftBracket); + case ']': + minus_state = MinusState::Operator; + return make_token(Token::Kind::RightBracket); + case '{': + return make_token(Token::Kind::LeftBrace); + case '}': + minus_state = MinusState::Operator; + return make_token(Token::Kind::RightBrace); + case '>': + if (pos < m_in.size() && m_in[pos] == '=') { + pos += 1; + return make_token(Token::Kind::GreaterEqual); + } + return make_token(Token::Kind::GreaterThan); + case '<': + if (pos < m_in.size() && m_in[pos] == '=') { + pos += 1; + return make_token(Token::Kind::LessEqual); } + return make_token(Token::Kind::LessThan); + case '=': + if (pos < m_in.size() && m_in[pos] == '=') { + pos += 1; + return make_token(Token::Kind::Equal); + } + return make_token(Token::Kind::Unknown); + case '!': + if (pos < m_in.size() && m_in[pos] == '=') { + pos += 1; + return make_token(Token::Kind::NotEqual); + } + return make_token(Token::Kind::Unknown); + case '\"': + return scan_string(); + case '0': + case '1': + case '2': + case '3': + case '4': + case '5': + case '6': + case '7': + case '8': + case '9': + minus_state = MinusState::Operator; + return scan_number(); + case '_': + case '@': + case '$': + minus_state = MinusState::Operator; + return scan_id(); + default: + return make_token(Token::Kind::Unknown); } } - void accept(NodeVisitor& v) const { - v.visit(*this); - } -}; - -class ExpressionListNode : public AstNode { -public: - std::vector> rpn_output; - - explicit ExpressionListNode() : AstNode(0) { } - explicit ExpressionListNode(size_t pos) : AstNode(pos) { } - - void accept(NodeVisitor& v) const { - v.visit(*this); + Token scan_id() { + for (;;) { + if (pos >= m_in.size()) { + break; + } + const char ch = m_in[pos]; + if (!std::isalnum(ch) && ch != '.' && ch != '/' && ch != '_' && ch != '-') { + break; + } + pos += 1; + } + return make_token(Token::Kind::Id); } -}; - -class StatementNode : public AstNode { -public: - StatementNode(size_t pos) : AstNode(pos) { } - - virtual void accept(NodeVisitor& v) const = 0; -}; - -class ForStatementNode : public StatementNode { -public: - ExpressionListNode condition; - BlockNode body; - BlockNode *const parent; - - ForStatementNode(BlockNode *const parent, size_t pos) : StatementNode(pos), parent(parent) { } - - virtual void accept(NodeVisitor& v) const = 0; -}; -class ForArrayStatementNode : public ForStatementNode { -public: - const std::string value; - - explicit ForArrayStatementNode(const std::string& value, BlockNode *const parent, size_t pos) : ForStatementNode(parent, pos), value(value) { } - - void accept(NodeVisitor& v) const { - v.visit(*this); + Token scan_number() { + for (;;) { + if (pos >= m_in.size()) { + break; + } + const char ch = m_in[pos]; + // be very permissive in lexer (we'll catch errors when conversion happens) + if (!std::isdigit(ch) && ch != '.' && ch != 'e' && ch != 'E' && ch != '+' && ch != '-') { + break; + } + pos += 1; + } + return make_token(Token::Kind::Number); } -}; - -class ForObjectStatementNode : public ForStatementNode { -public: - const std::string key; - const std::string value; - - explicit ForObjectStatementNode(const std::string& key, const std::string& value, BlockNode *const parent, size_t pos) : ForStatementNode(parent, pos), key(key), value(value) { } - void accept(NodeVisitor& v) const { - v.visit(*this); + Token scan_string() { + bool escape {false}; + for (;;) { + if (pos >= m_in.size()) { + break; + } + const char ch = m_in[pos++]; + if (ch == '\\') { + escape = true; + } else if (!escape && ch == m_in[tok_start]) { + break; + } else { + escape = false; + } + } + return make_token(Token::Kind::String); } -}; - -class IfStatementNode : public StatementNode { -public: - ExpressionListNode condition; - BlockNode true_statement; - BlockNode false_statement; - BlockNode *const parent; - - const bool is_nested; - bool has_false_statement {false}; - - explicit IfStatementNode(BlockNode *const parent, size_t pos) : StatementNode(pos), parent(parent), is_nested(false) { } - explicit IfStatementNode(bool is_nested, BlockNode *const parent, size_t pos) : StatementNode(pos), parent(parent), is_nested(is_nested) { } - void accept(NodeVisitor& v) const { - v.visit(*this); + Token make_token(Token::Kind kind) const { + return Token(kind, string_view::slice(m_in, tok_start, pos)); } -}; - -class IncludeStatementNode : public StatementNode { -public: - const std::string file; - - explicit IncludeStatementNode(const std::string& file, size_t pos) : StatementNode(pos), file(file) { } - - void accept(NodeVisitor& v) const { - v.visit(*this); - }; -}; - -class SetStatementNode : public StatementNode { -public: - const std::string key; - ExpressionListNode expression; - - explicit SetStatementNode(const std::string& key, size_t pos) : StatementNode(pos), key(key) { } - - void accept(NodeVisitor& v) const { - v.visit(*this); - }; -}; - -} // namespace inja - -#endif // INCLUDE_INJA_NODE_HPP_ - -// #include "template.hpp" -// Copyright (c) 2019 Pantor. All rights reserved. - -#ifndef INCLUDE_INJA_TEMPLATE_HPP_ -#define INCLUDE_INJA_TEMPLATE_HPP_ - -#include -#include -#include -#include - -// #include "node.hpp" - -// #include "statistics.hpp" -// Copyright (c) 2019 Pantor. All rights reserved. - -#ifndef INCLUDE_INJA_STATISTICS_HPP_ -#define INCLUDE_INJA_STATISTICS_HPP_ - -// #include "node.hpp" - - - -namespace inja { -/*! - * \brief A class for counting statistics on a Template. - */ -class StatisticsVisitor : public NodeVisitor { - void visit(const BlockNode& node) { - for (auto& n : node.nodes) { - n->accept(*this); + void skip_whitespaces_and_newlines() { + if (pos < m_in.size()) { + while (pos < m_in.size() && (m_in[pos] == ' ' || m_in[pos] == '\t' || m_in[pos] == '\n' || m_in[pos] == '\r')) { + pos += 1; + } } } - void visit(const TextNode&) { } - void visit(const ExpressionNode&) { } - void visit(const LiteralNode&) { } + void skip_whitespaces_and_first_newline() { + if (pos < m_in.size()) { + while (pos < m_in.size() && (m_in[pos] == ' ' || m_in[pos] == '\t')) { + pos += 1; + } + } - void visit(const JsonNode&) { - variable_counter += 1; + if (pos < m_in.size()) { + const char ch = m_in[pos]; + if (ch == '\n') { + pos += 1; + } else if (ch == '\r') { + pos += 1; + if (pos < m_in.size() && m_in[pos] == '\n') { + pos += 1; + } + } + } } - void visit(const FunctionNode&) { } - - void visit(const ExpressionListNode& node) { - for (auto& n : node.rpn_output) { - n->accept(*this); + static std::string_view clear_final_line_if_whitespace(std::string_view text) { + std::string_view result = text; + while (!result.empty()) { + const char ch = result.back(); + if (ch == ' ' || ch == '\t') { + result.remove_suffix(1); + } else if (ch == '\n' || ch == '\r') { + break; + } else { + return text; + } } + return result; } - void visit(const StatementNode&) { } - void visit(const ForStatementNode&) { } +public: + explicit Lexer(const LexerConfig& config): config(config), state(State::Text), minus_state(MinusState::Number) {} - void visit(const ForArrayStatementNode& node) { - node.condition.accept(*this); - node.body.accept(*this); + SourceLocation current_position() const { + return get_source_location(m_in, tok_start); } - void visit(const ForObjectStatementNode& node) { - node.condition.accept(*this); - node.body.accept(*this); - } + void start(std::string_view input) { + m_in = input; + tok_start = 0; + pos = 0; + state = State::Text; + minus_state = MinusState::Number; - void visit(const IfStatementNode& node) { - node.condition.accept(*this); - node.true_statement.accept(*this); - node.false_statement.accept(*this); + // Consume byte order mark (BOM) for UTF-8 + if (inja::string_view::starts_with(m_in, "\xEF\xBB\xBF")) { + m_in = m_in.substr(3); + } } - void visit(const IncludeStatementNode&) { } - - void visit(const SetStatementNode&) { } - -public: - unsigned int variable_counter; + Token scan() { + tok_start = pos; - explicit StatisticsVisitor() : variable_counter(0) { } -}; + again: + if (tok_start >= m_in.size()) { + return make_token(Token::Kind::Eof); + } -} // namespace inja + switch (state) { + default: + case State::Text: { + // fast-scan to first open character + const size_t open_start = m_in.substr(pos).find_first_of(config.open_chars); + if (open_start == std::string_view::npos) { + // didn't find open, return remaining text as text token + pos = m_in.size(); + return make_token(Token::Kind::Text); + } + pos += open_start; -#endif // INCLUDE_INJA_STATISTICS_HPP_ + // try to match one of the opening sequences, and get the close + std::string_view open_str = m_in.substr(pos); + bool must_lstrip = false; + if (inja::string_view::starts_with(open_str, config.expression_open)) { + if (inja::string_view::starts_with(open_str, config.expression_open_force_lstrip)) { + state = State::ExpressionStartForceLstrip; + must_lstrip = true; + } else { + state = State::ExpressionStart; + } + } else if (inja::string_view::starts_with(open_str, config.statement_open)) { + if (inja::string_view::starts_with(open_str, config.statement_open_no_lstrip)) { + state = State::StatementStartNoLstrip; + } else if (inja::string_view::starts_with(open_str, config.statement_open_force_lstrip)) { + state = State::StatementStartForceLstrip; + must_lstrip = true; + } else { + state = State::StatementStart; + must_lstrip = config.lstrip_blocks; + } + } else if (inja::string_view::starts_with(open_str, config.comment_open)) { + if (inja::string_view::starts_with(open_str, config.comment_open_force_lstrip)) { + state = State::CommentStartForceLstrip; + must_lstrip = true; + } else { + state = State::CommentStart; + must_lstrip = config.lstrip_blocks; + } + } else if ((pos == 0 || m_in[pos - 1] == '\n') && inja::string_view::starts_with(open_str, config.line_statement)) { + state = State::LineStart; + } else { + pos += 1; // wasn't actually an opening sequence + goto again; + } + std::string_view text = string_view::slice(m_in, tok_start, pos); + if (must_lstrip) { + text = clear_final_line_if_whitespace(text); + } + if (text.empty()) { + goto again; // don't generate empty token + } + return Token(Token::Kind::Text, text); + } + case State::ExpressionStart: { + state = State::ExpressionBody; + pos += config.expression_open.size(); + return make_token(Token::Kind::ExpressionOpen); + } + case State::ExpressionStartForceLstrip: { + state = State::ExpressionBody; + pos += config.expression_open_force_lstrip.size(); + return make_token(Token::Kind::ExpressionOpen); + } + case State::LineStart: { + state = State::LineBody; + pos += config.line_statement.size(); + return make_token(Token::Kind::LineStatementOpen); + } + case State::StatementStart: { + state = State::StatementBody; + pos += config.statement_open.size(); + return make_token(Token::Kind::StatementOpen); + } + case State::StatementStartNoLstrip: { + state = State::StatementBody; + pos += config.statement_open_no_lstrip.size(); + return make_token(Token::Kind::StatementOpen); + } + case State::StatementStartForceLstrip: { + state = State::StatementBody; + pos += config.statement_open_force_lstrip.size(); + return make_token(Token::Kind::StatementOpen); + } + case State::CommentStart: { + state = State::CommentBody; + pos += config.comment_open.size(); + return make_token(Token::Kind::CommentOpen); + } + case State::CommentStartForceLstrip: { + state = State::CommentBody; + pos += config.comment_open_force_lstrip.size(); + return make_token(Token::Kind::CommentOpen); + } + case State::ExpressionBody: + return scan_body(config.expression_close, Token::Kind::ExpressionClose, config.expression_close_force_rstrip); + case State::LineBody: + return scan_body("\n", Token::Kind::LineStatementClose); + case State::StatementBody: + return scan_body(config.statement_close, Token::Kind::StatementClose, config.statement_close_force_rstrip, config.trim_blocks); + case State::CommentBody: { + // fast-scan to comment close + const size_t end = m_in.substr(pos).find(config.comment_close); + if (end == std::string_view::npos) { + pos = m_in.size(); + return make_token(Token::Kind::Eof); + } -namespace inja { + // Check for trim pattern + const bool must_rstrip = inja::string_view::starts_with(m_in.substr(pos + end - 1), config.comment_close_force_rstrip); -/*! - * \brief The main inja Template. - */ -struct Template { - BlockNode root; - std::string content; + // return the entire comment in the close token + state = State::Text; + pos += end + config.comment_close.size(); + Token tok = make_token(Token::Kind::CommentClose); - explicit Template() { } - explicit Template(const std::string& content): content(content) { } + if (must_rstrip || config.trim_blocks) { + skip_whitespaces_and_first_newline(); + } + return tok; + } + } + } - /// Return number of variables (total number, not distinct ones) in the template - int count_variables() { - auto statistic_visitor = StatisticsVisitor(); - root.accept(statistic_visitor); - return statistic_visitor.variable_counter; + const LexerConfig& get_config() const { + return config; } }; -using TemplateStorage = std::map; - } // namespace inja -#endif // INCLUDE_INJA_TEMPLATE_HPP_ +#endif // INCLUDE_INJA_LEXER_HPP_ + +// #include "node.hpp" + +// #include "template.hpp" // #include "token.hpp" // #include "utils.hpp" -#include - namespace inja { /*! * \brief Class for parsing an inja Template. */ class Parser { - const ParserConfig &config; + const ParserConfig& config; Lexer lexer; - TemplateStorage &template_storage; - const FunctionStorage &function_storage; + TemplateStorage& template_storage; + const FunctionStorage& function_storage; Token tok, peek_tok; bool have_peek_tok {false}; @@ -2776,21 +1449,23 @@ class Parser { size_t current_bracket_level {0}; size_t current_brace_level {0}; - nonstd::string_view json_literal_start; + std::string_view literal_start; - BlockNode *current_block {nullptr}; - ExpressionListNode *current_expression_list {nullptr}; + BlockNode* current_block {nullptr}; + ExpressionListNode* current_expression_list {nullptr}; std::stack> function_stack; + std::vector> arguments; std::stack> operator_stack; std::stack if_statement_stack; std::stack for_statement_stack; + std::stack block_statement_stack; - void throw_parser_error(const std::string &message) { + inline void throw_parser_error(const std::string& message) const { INJA_THROW(ParserError(message, lexer.current_position())); } - void get_next_token() { + inline void get_next_token() { if (have_peek_tok) { tok = peek_tok; have_peek_tok = false; @@ -2799,49 +1474,108 @@ class Parser { } } - void get_peek_token() { + inline void get_peek_token() { if (!have_peek_tok) { peek_tok = lexer.scan(); have_peek_tok = true; } } - void add_json_literal(const char* content_ptr) { - nonstd::string_view json_text(json_literal_start.data(), tok.text.data() - json_literal_start.data() + tok.text.size()); - current_expression_list->rpn_output.emplace_back(std::make_shared(json::parse(json_text), json_text.data() - content_ptr)); + inline void add_literal(const char* content_ptr) { + std::string_view data_text(literal_start.data(), tok.text.data() - literal_start.data() + tok.text.size()); + arguments.emplace_back(std::make_shared(data_text, data_text.data() - content_ptr)); + } + + inline void add_operator() { + auto function = operator_stack.top(); + operator_stack.pop(); + + for (int i = 0; i < function->number_args; ++i) { + function->arguments.insert(function->arguments.begin(), arguments.back()); + arguments.pop_back(); + } + arguments.emplace_back(function); + } + + void add_to_template_storage(std::string_view path, std::string& template_name) { + if (template_storage.find(template_name) != template_storage.end()) { + return; + } + + std::string original_path = static_cast(path); + std::string original_name = template_name; + + if (config.search_included_templates_in_files) { + // Build the relative path + template_name = original_path + original_name; + if (template_name.compare(0, 2, "./") == 0) { + template_name.erase(0, 2); + } + + if (template_storage.find(template_name) == template_storage.end()) { + // Load file + std::ifstream file; + file.open(template_name); + if (!file.fail()) { + std::string text((std::istreambuf_iterator(file)), std::istreambuf_iterator()); + + auto include_template = Template(text); + template_storage.emplace(template_name, include_template); + parse_into_template(template_storage[template_name], template_name); + return; + } else if (!config.include_callback) { + INJA_THROW(FileError("failed accessing file at '" + template_name + "'")); + } + } + } + + // Try include callback + if (config.include_callback) { + auto include_template = config.include_callback(original_path, original_name); + template_storage.emplace(template_name, include_template); + } + } + + std::string parse_filename(const Token& tok) const { + if (tok.kind != Token::Kind::String) { + throw_parser_error("expected string, got '" + tok.describe() + "'"); + } + + if (tok.text.length() < 2) { + throw_parser_error("expected filename, got '" + static_cast(tok.text) + "'"); + } + + // Remove first and last character "" + return std::string {tok.text.substr(1, tok.text.length() - 2)}; } - bool parse_expression(Template &tmpl, Token::Kind closing) { + bool parse_expression(Template& tmpl, Token::Kind closing) { while (tok.kind != closing && tok.kind != Token::Kind::Eof) { // Literals switch (tok.kind) { case Token::Kind::String: { if (current_brace_level == 0 && current_bracket_level == 0) { - json_literal_start = tok.text; - add_json_literal(tmpl.content.c_str()); + literal_start = tok.text; + add_literal(tmpl.content.c_str()); } - } break; case Token::Kind::Number: { if (current_brace_level == 0 && current_bracket_level == 0) { - json_literal_start = tok.text; - add_json_literal(tmpl.content.c_str()); + literal_start = tok.text; + add_literal(tmpl.content.c_str()); } - } break; case Token::Kind::LeftBracket: { if (current_brace_level == 0 && current_bracket_level == 0) { - json_literal_start = tok.text; + literal_start = tok.text; } current_bracket_level += 1; - } break; case Token::Kind::LeftBrace: { if (current_brace_level == 0 && current_bracket_level == 0) { - json_literal_start = tok.text; + literal_start = tok.text; } current_brace_level += 1; - } break; case Token::Kind::RightBracket: { if (current_bracket_level == 0) { @@ -2850,9 +1584,8 @@ class Parser { current_bracket_level -= 1; if (current_brace_level == 0 && current_bracket_level == 0) { - add_json_literal(tmpl.content.c_str()); + add_literal(tmpl.content.c_str()); } - } break; case Token::Kind::RightBrace: { if (current_brace_level == 0) { @@ -2861,35 +1594,35 @@ class Parser { current_brace_level -= 1; if (current_brace_level == 0 && current_bracket_level == 0) { - add_json_literal(tmpl.content.c_str()); + add_literal(tmpl.content.c_str()); } - } break; case Token::Kind::Id: { get_peek_token(); - // Json Literal - if (tok.text == static_cast("true") || tok.text == static_cast("false") || tok.text == static_cast("null")) { + // Data Literal + if (tok.text == static_cast("true") || tok.text == static_cast("false") || + tok.text == static_cast("null")) { if (current_brace_level == 0 && current_bracket_level == 0) { - json_literal_start = tok.text; - add_json_literal(tmpl.content.c_str()); + literal_start = tok.text; + add_literal(tmpl.content.c_str()); } - // Operator + // Operator } else if (tok.text == "and" || tok.text == "or" || tok.text == "in" || tok.text == "not") { goto parse_operator; - // Functions + // Functions } else if (peek_tok.kind == Token::Kind::LeftParen) { operator_stack.emplace(std::make_shared(static_cast(tok.text), tok.text.data() - tmpl.content.c_str())); - function_stack.emplace(operator_stack.top().get(), current_paren_level); + function_stack.emplace(operator_stack.top().get(), current_paren_level); - // Variables + // Variables } else { - current_expression_list->rpn_output.emplace_back(std::make_shared(static_cast(tok.text), tok.text.data() - tmpl.content.c_str())); + arguments.emplace_back(std::make_shared(static_cast(tok.text), tok.text.data() - tmpl.content.c_str())); } - // Operators + // Operators } break; case Token::Kind::Equal: case Token::Kind::NotEqual: @@ -2905,7 +1638,7 @@ class Parser { case Token::Kind::Percent: case Token::Kind::Dot: { - parse_operator: + parse_operator: FunctionStorage::Operation operation; switch (tok.kind) { case Token::Kind::Id: { @@ -2966,13 +1699,14 @@ class Parser { } auto function_node = std::make_shared(operation, tok.text.data() - tmpl.content.c_str()); - while (!operator_stack.empty() && ((operator_stack.top()->precedence > function_node->precedence) || (operator_stack.top()->precedence == function_node->precedence && function_node->associativity == FunctionNode::Associativity::Left)) && (operator_stack.top()->operation != FunctionStorage::Operation::ParenLeft)) { - current_expression_list->rpn_output.emplace_back(operator_stack.top()); - operator_stack.pop(); + while (!operator_stack.empty() && + ((operator_stack.top()->precedence > function_node->precedence) || + (operator_stack.top()->precedence == function_node->precedence && function_node->associativity == FunctionNode::Associativity::Left)) && + (operator_stack.top()->operation != FunctionStorage::Operation::ParenLeft)) { + add_operator(); } operator_stack.emplace(function_node); - } break; case Token::Kind::Comma: { if (current_brace_level == 0 && current_bracket_level == 0) { @@ -2982,13 +1716,11 @@ class Parser { function_stack.top().first->number_args += 1; } - } break; case Token::Kind::Colon: { if (current_brace_level == 0 && current_bracket_level == 0) { throw_parser_error("unexpected ':'"); } - } break; case Token::Kind::LeftParen: { current_paren_level += 1; @@ -3000,13 +1732,11 @@ class Parser { function_stack.top().first->number_args = 0; } } - } break; case Token::Kind::RightParen: { current_paren_level -= 1; while (!operator_stack.empty() && operator_stack.top()->operation != FunctionStorage::Operation::ParenLeft) { - current_expression_list->rpn_output.emplace_back(operator_stack.top()); - operator_stack.pop(); + add_operator(); } if (!operator_stack.empty() && operator_stack.top()->operation == FunctionStorage::Operation::ParenLeft) { @@ -3028,8 +1758,7 @@ class Parser { throw_parser_error("internal error at function " + func->name); } - current_expression_list->rpn_output.emplace_back(operator_stack.top()); - operator_stack.pop(); + add_operator(); function_stack.pop(); } } @@ -3041,14 +1770,20 @@ class Parser { } while (!operator_stack.empty()) { - current_expression_list->rpn_output.emplace_back(operator_stack.top()); - operator_stack.pop(); + add_operator(); + } + + if (arguments.size() == 1) { + current_expression_list->root = arguments[0]; + arguments = {}; + } else if (arguments.size() > 1) { + throw_parser_error("malformed expression"); } return true; } - bool parse_statement(Template &tmpl, Token::Kind closing, nonstd::string_view path) { + bool parse_statement(Template& tmpl, Token::Kind closing, std::string_view path) { if (tok.kind != Token::Kind::Id) { return false; } @@ -3065,12 +1800,11 @@ class Parser { if (!parse_expression(tmpl, closing)) { return false; } - } else if (tok.text == static_cast("else")) { if (if_statement_stack.empty()) { throw_parser_error("else without matching if"); } - auto &if_statement_data = if_statement_stack.top(); + auto& if_statement_data = if_statement_stack.top(); get_next_token(); if_statement_data->has_false_statement = true; @@ -3090,7 +1824,6 @@ class Parser { return false; } } - } else if (tok.text == static_cast("endif")) { if (if_statement_stack.empty()) { throw_parser_error("endif without matching if"); @@ -3101,12 +1834,40 @@ class Parser { if_statement_stack.pop(); } - auto &if_statement_data = if_statement_stack.top(); + auto& if_statement_data = if_statement_stack.top(); get_next_token(); current_block = if_statement_data->parent; if_statement_stack.pop(); + } else if (tok.text == static_cast("block")) { + get_next_token(); + + if (tok.kind != Token::Kind::Id) { + throw_parser_error("expected block name, got '" + tok.describe() + "'"); + } + + const std::string block_name = static_cast(tok.text); + + auto block_statement_node = std::make_shared(current_block, block_name, tok.text.data() - tmpl.content.c_str()); + current_block->nodes.emplace_back(block_statement_node); + block_statement_stack.emplace(block_statement_node.get()); + current_block = &block_statement_node->block; + auto success = tmpl.block_storage.emplace(block_name, block_statement_node); + if (!success.second) { + throw_parser_error("block with the name '" + block_name + "' does already exist"); + } + + get_next_token(); + } else if (tok.text == static_cast("endblock")) { + if (block_statement_stack.empty()) { + throw_parser_error("endblock without matching block"); + } + + auto& block_statement_data = block_statement_stack.top(); + get_next_token(); + current_block = block_statement_data->parent; + block_statement_stack.pop(); } else if (tok.text == static_cast("for")) { get_next_token(); @@ -3130,11 +1891,13 @@ class Parser { value_token = tok; get_next_token(); - for_statement_node = std::make_shared(static_cast(key_token.text), static_cast(value_token.text), current_block, tok.text.data() - tmpl.content.c_str()); + for_statement_node = std::make_shared(static_cast(key_token.text), static_cast(value_token.text), + current_block, tok.text.data() - tmpl.content.c_str()); - // Array type + // Array type } else { - for_statement_node = std::make_shared(static_cast(value_token.text), current_block, tok.text.data() - tmpl.content.c_str()); + for_statement_node = + std::make_shared(static_cast(value_token.text), current_block, tok.text.data() - tmpl.content.c_str()); } current_block->nodes.emplace_back(for_statement_node); @@ -3150,44 +1913,34 @@ class Parser { if (!parse_expression(tmpl, closing)) { return false; } - } else if (tok.text == static_cast("endfor")) { if (for_statement_stack.empty()) { throw_parser_error("endfor without matching for"); } - auto &for_statement_data = for_statement_stack.top(); + auto& for_statement_data = for_statement_stack.top(); get_next_token(); current_block = for_statement_data->parent; for_statement_stack.pop(); - } else if (tok.text == static_cast("include")) { get_next_token(); - if (tok.kind != Token::Kind::String) { - throw_parser_error("expected string, got '" + tok.describe() + "'"); - } + std::string template_name = parse_filename(tok); + add_to_template_storage(path, template_name); - // Build the relative path - json json_name = json::parse(tok.text); - std::string pathname = static_cast(path); - pathname += json_name.get_ref(); - if (pathname.compare(0, 2, "./") == 0) { - pathname.erase(0, 2); - } - // sys::path::remove_dots(pathname, true, sys::path::Style::posix); + current_block->nodes.emplace_back(std::make_shared(template_name, tok.text.data() - tmpl.content.c_str())); - if (config.search_included_templates_in_files && template_storage.find(pathname) == template_storage.end()) { - auto include_template = Template(load_file(pathname)); - template_storage.emplace(pathname, include_template); - parse_into_template(template_storage[pathname], pathname); - } + get_next_token(); + } else if (tok.text == static_cast("extends")) { + get_next_token(); - current_block->nodes.emplace_back(std::make_shared(pathname, tok.text.data() - tmpl.content.c_str())); + std::string template_name = parse_filename(tok); + add_to_template_storage(path, template_name); - get_next_token(); + current_block->nodes.emplace_back(std::make_shared(template_name, tok.text.data() - tmpl.content.c_str())); + get_next_token(); } else if (tok.text == static_cast("set")) { get_next_token(); @@ -3210,14 +1963,13 @@ class Parser { if (!parse_expression(tmpl, closing)) { return false; } - } else { return false; } return true; } - void parse_into(Template &tmpl, nonstd::string_view path) { + void parse_into(Template& tmpl, std::string_view path) { lexer.start(tmpl.content); current_block = &tmpl.root; @@ -3231,7 +1983,8 @@ class Parser { if (!for_statement_stack.empty()) { throw_parser_error("unmatched for"); } - } return; + } + return; case Token::Kind::Text: { current_block->nodes.emplace_back(std::make_shared(tok.text.data() - tmpl.content.c_str(), tok.text.size())); } break; @@ -3281,33 +2034,35 @@ class Parser { } } - public: - explicit Parser(const ParserConfig &parser_config, const LexerConfig &lexer_config, - TemplateStorage &template_storage, const FunctionStorage &function_storage) - : config(parser_config), lexer(lexer_config), template_storage(template_storage), function_storage(function_storage) { } + explicit Parser(const ParserConfig& parser_config, const LexerConfig& lexer_config, TemplateStorage& template_storage, + const FunctionStorage& function_storage) + : config(parser_config), lexer(lexer_config), template_storage(template_storage), function_storage(function_storage) {} - Template parse(nonstd::string_view input, nonstd::string_view path) { + Template parse(std::string_view input, std::string_view path) { auto result = Template(static_cast(input)); parse_into(result, path); return result; } - Template parse(nonstd::string_view input) { + Template parse(std::string_view input) { return parse(input, "./"); } - void parse_into_template(Template& tmpl, nonstd::string_view filename) { - nonstd::string_view path = filename.substr(0, filename.find_last_of("/\\") + 1); + void parse_into_template(Template& tmpl, std::string_view filename) { + std::string_view path = filename.substr(0, filename.find_last_of("/\\") + 1); // StringRef path = sys::path::parent_path(filename); auto sub_parser = Parser(config, lexer.get_config(), template_storage, function_storage); sub_parser.parse_into(tmpl, path); } - std::string load_file(nonstd::string_view filename) { + std::string load_file(const std::string& filename) { std::ifstream file; - open_file_or_throw(static_cast(filename), file); + file.open(filename); + if (file.fail()) { + INJA_THROW(FileError("failed accessing file at '" + filename + "'")); + } std::string text((std::istreambuf_iterator(file)), std::istreambuf_iterator()); return text; } @@ -3318,8 +2073,6 @@ public: #endif // INCLUDE_INJA_PARSER_HPP_ // #include "renderer.hpp" -// Copyright (c) 2020 Pantor. All rights reserved. - #ifndef INCLUDE_INJA_RENDERER_HPP_ #define INCLUDE_INJA_RENDERER_HPP_ @@ -3329,8 +2082,6 @@ public: #include #include -#include - // #include "config.hpp" // #include "exceptions.hpp" @@ -3347,25 +2098,31 @@ namespace inja { /*! * \brief Class for rendering a Template with data. */ -class Renderer : public NodeVisitor { +class Renderer : public NodeVisitor { using Op = FunctionStorage::Operation; const RenderConfig config; - const Template *current_template; - const TemplateStorage &template_storage; - const FunctionStorage &function_storage; + const TemplateStorage& template_storage; + const FunctionStorage& function_storage; + + const Template* current_template; + size_t current_level {0}; + std::vector template_stack; + std::vector block_statement_stack; - const json *json_input; - std::ostream *output_stream; + const json* data_input; + std::ostream* output_stream; - json json_additional_data; - json* current_loop_data = &json_additional_data["loop"]; + json additional_data; + json* current_loop_data = &additional_data["loop"]; - std::vector> json_tmp_stack; - std::stack json_eval_stack; - std::stack not_found_stack; + std::vector> data_tmp_stack; + std::stack data_eval_stack; + std::stack not_found_stack; - bool truthy(const json* data) const { + bool break_rendering {false}; + + static bool truthy(const json* data) { if (data->is_boolean()) { return data->get(); } else if (data->is_number()) { @@ -3376,7 +2133,7 @@ class Renderer : public NodeVisitor { return !data->empty(); } - void print_json(const std::shared_ptr value) { + void print_data(const std::shared_ptr value) { if (value->is_string()) { *output_stream << value->get_ref(); } else if (value->is_number_integer()) { @@ -3388,18 +2145,20 @@ class Renderer : public NodeVisitor { } const std::shared_ptr eval_expression_list(const ExpressionListNode& expression_list) { - for (auto& expression : expression_list.rpn_output) { - expression->accept(*this); + if (!expression_list.root) { + throw_renderer_error("empty expression", expression_list); } - if (json_eval_stack.empty()) { + expression_list.root->accept(*this); + + if (data_eval_stack.empty()) { throw_renderer_error("empty expression", expression_list); - } else if (json_eval_stack.size() != 1) { + } else if (data_eval_stack.size() != 1) { throw_renderer_error("malformed expression", expression_list); } - auto result = json_eval_stack.top(); - json_eval_stack.pop(); + const auto result = data_eval_stack.top(); + data_eval_stack.pop(); if (!result) { if (not_found_stack.empty()) { @@ -3414,51 +2173,68 @@ class Renderer : public NodeVisitor { return std::make_shared(*result); } - void throw_renderer_error(const std::string &message, const AstNode& node) { + void throw_renderer_error(const std::string& message, const AstNode& node) { SourceLocation loc = get_source_location(current_template->content, node.pos); INJA_THROW(RenderError(message, loc)); } - template - std::array get_arguments(const AstNode& node) { - if (json_eval_stack.size() < N) { - throw_renderer_error("function needs " + std::to_string(N) + " variables, but has only found " + std::to_string(json_eval_stack.size()), node); + void make_result(const json&& result) { + auto result_ptr = std::make_shared(result); + data_tmp_stack.push_back(result_ptr); + data_eval_stack.push(result_ptr.get()); + } + + template std::array get_arguments(const FunctionNode& node) { + if (node.arguments.size() < N_start + N) { + throw_renderer_error("function needs " + std::to_string(N_start + N) + " variables, but has only found " + std::to_string(node.arguments.size()), node); + } + + for (size_t i = N_start; i < N_start + N; i += 1) { + node.arguments[i]->accept(*this); + } + + if (data_eval_stack.size() < N) { + throw_renderer_error("function needs " + std::to_string(N) + " variables, but has only found " + std::to_string(data_eval_stack.size()), node); } std::array result; for (size_t i = 0; i < N; i += 1) { - result[N - i - 1] = json_eval_stack.top(); - json_eval_stack.pop(); + result[N - i - 1] = data_eval_stack.top(); + data_eval_stack.pop(); if (!result[N - i - 1]) { - auto json_node = not_found_stack.top(); + const auto data_node = not_found_stack.top(); not_found_stack.pop(); if (throw_not_found) { - throw_renderer_error("variable '" + static_cast(json_node->name) + "' not found", *json_node); + throw_renderer_error("variable '" + static_cast(data_node->name) + "' not found", *data_node); } } } return result; } - template - Arguments get_argument_vector(size_t N, const AstNode& node) { - if (json_eval_stack.size() < N) { - throw_renderer_error("function needs " + std::to_string(N) + " variables, but has only found " + std::to_string(json_eval_stack.size()), node); + template Arguments get_argument_vector(const FunctionNode& node) { + const size_t N = node.arguments.size(); + for (auto a : node.arguments) { + a->accept(*this); + } + + if (data_eval_stack.size() < N) { + throw_renderer_error("function needs " + std::to_string(N) + " variables, but has only found " + std::to_string(data_eval_stack.size()), node); } Arguments result {N}; for (size_t i = 0; i < N; i += 1) { - result[N - i - 1] = json_eval_stack.top(); - json_eval_stack.pop(); + result[N - i - 1] = data_eval_stack.top(); + data_eval_stack.pop(); if (!result[N - i - 1]) { - auto json_node = not_found_stack.top(); + const auto data_node = not_found_stack.top(); not_found_stack.pop(); if (throw_not_found) { - throw_renderer_error("variable '" + static_cast(json_node->name) + "' not found", *json_node); + throw_renderer_error("variable '" + static_cast(data_node->name) + "' not found", *data_node); } } } @@ -3468,6 +2244,10 @@ class Renderer : public NodeVisitor { void visit(const BlockNode& node) { for (auto& n : node.nodes) { n->accept(*this); + + if (break_rendering) { + break; + } } } @@ -3475,325 +2255,294 @@ class Renderer : public NodeVisitor { output_stream->write(current_template->content.c_str() + node.pos, node.length); } - void visit(const ExpressionNode&) { } + void visit(const ExpressionNode&) {} void visit(const LiteralNode& node) { - json_eval_stack.push(&node.value); + data_eval_stack.push(&node.value); } - void visit(const JsonNode& node) { - if (json_additional_data.contains(node.ptr)) { - json_eval_stack.push(&(json_additional_data[node.ptr])); - - } else if (json_input->contains(node.ptr)) { - json_eval_stack.push(&(*json_input)[node.ptr]); - + void visit(const DataNode& node) { + if (additional_data.contains(node.ptr)) { + data_eval_stack.push(&(additional_data[node.ptr])); + } else if (data_input->contains(node.ptr)) { + data_eval_stack.push(&(*data_input)[node.ptr]); } else { // Try to evaluate as a no-argument callback - auto function_data = function_storage.find_function(node.name, 0); + const auto function_data = function_storage.find_function(node.name, 0); if (function_data.operation == FunctionStorage::Operation::Callback) { Arguments empty_args {}; - auto value = std::make_shared(function_data.callback(empty_args)); - json_tmp_stack.push_back(value); - json_eval_stack.push(value.get()); - + const auto value = std::make_shared(function_data.callback(empty_args)); + data_tmp_stack.push_back(value); + data_eval_stack.push(value.get()); } else { - json_eval_stack.push(nullptr); + data_eval_stack.push(nullptr); not_found_stack.emplace(&node); } } } void visit(const FunctionNode& node) { - std::shared_ptr result_ptr; - switch (node.operation) { case Op::Not: { - auto args = get_arguments<1>(node); - result_ptr = std::make_shared(!truthy(args[0])); - json_tmp_stack.push_back(result_ptr); - json_eval_stack.push(result_ptr.get()); + const auto args = get_arguments<1>(node); + make_result(!truthy(args[0])); } break; case Op::And: { - auto args = get_arguments<2>(node); - result_ptr = std::make_shared(truthy(args[0]) && truthy(args[1])); - json_tmp_stack.push_back(result_ptr); - json_eval_stack.push(result_ptr.get()); + make_result(truthy(get_arguments<1, 0>(node)[0]) && truthy(get_arguments<1, 1>(node)[0])); } break; case Op::Or: { - auto args = get_arguments<2>(node); - result_ptr = std::make_shared(truthy(args[0]) || truthy(args[1])); - json_tmp_stack.push_back(result_ptr); - json_eval_stack.push(result_ptr.get()); + make_result(truthy(get_arguments<1, 0>(node)[0]) || truthy(get_arguments<1, 1>(node)[0])); } break; case Op::In: { - auto args = get_arguments<2>(node); - result_ptr = std::make_shared(std::find(args[1]->begin(), args[1]->end(), *args[0]) != args[1]->end()); - json_tmp_stack.push_back(result_ptr); - json_eval_stack.push(result_ptr.get()); + const auto args = get_arguments<2>(node); + make_result(std::find(args[1]->begin(), args[1]->end(), *args[0]) != args[1]->end()); } break; case Op::Equal: { - auto args = get_arguments<2>(node); - result_ptr = std::make_shared(*args[0] == *args[1]); - json_tmp_stack.push_back(result_ptr); - json_eval_stack.push(result_ptr.get()); + const auto args = get_arguments<2>(node); + make_result(*args[0] == *args[1]); } break; case Op::NotEqual: { - auto args = get_arguments<2>(node); - result_ptr = std::make_shared(*args[0] != *args[1]); - json_tmp_stack.push_back(result_ptr); - json_eval_stack.push(result_ptr.get()); + const auto args = get_arguments<2>(node); + make_result(*args[0] != *args[1]); } break; case Op::Greater: { - auto args = get_arguments<2>(node); - result_ptr = std::make_shared(*args[0] > *args[1]); - json_tmp_stack.push_back(result_ptr); - json_eval_stack.push(result_ptr.get()); + const auto args = get_arguments<2>(node); + make_result(*args[0] > *args[1]); } break; case Op::GreaterEqual: { - auto args = get_arguments<2>(node); - result_ptr = std::make_shared(*args[0] >= *args[1]); - json_tmp_stack.push_back(result_ptr); - json_eval_stack.push(result_ptr.get()); + const auto args = get_arguments<2>(node); + make_result(*args[0] >= *args[1]); } break; case Op::Less: { - auto args = get_arguments<2>(node); - result_ptr = std::make_shared(*args[0] < *args[1]); - json_tmp_stack.push_back(result_ptr); - json_eval_stack.push(result_ptr.get()); + const auto args = get_arguments<2>(node); + make_result(*args[0] < *args[1]); } break; case Op::LessEqual: { - auto args = get_arguments<2>(node); - result_ptr = std::make_shared(*args[0] <= *args[1]); - json_tmp_stack.push_back(result_ptr); - json_eval_stack.push(result_ptr.get()); + const auto args = get_arguments<2>(node); + make_result(*args[0] <= *args[1]); } break; case Op::Add: { - auto args = get_arguments<2>(node); + const auto args = get_arguments<2>(node); if (args[0]->is_string() && args[1]->is_string()) { - result_ptr = std::make_shared(args[0]->get_ref() + args[1]->get_ref()); - json_tmp_stack.push_back(result_ptr); + make_result(args[0]->get_ref() + args[1]->get_ref()); } else if (args[0]->is_number_integer() && args[1]->is_number_integer()) { - result_ptr = std::make_shared(args[0]->get() + args[1]->get()); - json_tmp_stack.push_back(result_ptr); + make_result(args[0]->get() + args[1]->get()); } else { - result_ptr = std::make_shared(args[0]->get() + args[1]->get()); - json_tmp_stack.push_back(result_ptr); + make_result(args[0]->get() + args[1]->get()); } - json_eval_stack.push(result_ptr.get()); } break; case Op::Subtract: { - auto args = get_arguments<2>(node); + const auto args = get_arguments<2>(node); if (args[0]->is_number_integer() && args[1]->is_number_integer()) { - result_ptr = std::make_shared(args[0]->get() - args[1]->get()); - json_tmp_stack.push_back(result_ptr); + make_result(args[0]->get() - args[1]->get()); } else { - result_ptr = std::make_shared(args[0]->get() - args[1]->get()); - json_tmp_stack.push_back(result_ptr); + make_result(args[0]->get() - args[1]->get()); } - json_eval_stack.push(result_ptr.get()); } break; case Op::Multiplication: { - auto args = get_arguments<2>(node); + const auto args = get_arguments<2>(node); if (args[0]->is_number_integer() && args[1]->is_number_integer()) { - result_ptr = std::make_shared(args[0]->get() * args[1]->get()); - json_tmp_stack.push_back(result_ptr); + make_result(args[0]->get() * args[1]->get()); } else { - result_ptr = std::make_shared(args[0]->get() * args[1]->get()); - json_tmp_stack.push_back(result_ptr); + make_result(args[0]->get() * args[1]->get()); } - json_eval_stack.push(result_ptr.get()); } break; case Op::Division: { - auto args = get_arguments<2>(node); + const auto args = get_arguments<2>(node); if (args[1]->get() == 0) { throw_renderer_error("division by zero", node); } - result_ptr = std::make_shared(args[0]->get() / args[1]->get()); - json_tmp_stack.push_back(result_ptr); - json_eval_stack.push(result_ptr.get()); + make_result(args[0]->get() / args[1]->get()); } break; case Op::Power: { - auto args = get_arguments<2>(node); + const auto args = get_arguments<2>(node); if (args[0]->is_number_integer() && args[1]->get() >= 0) { - int result = std::pow(args[0]->get(), args[1]->get()); - result_ptr = std::make_shared(std::move(result)); - json_tmp_stack.push_back(result_ptr); + int result = static_cast(std::pow(args[0]->get(), args[1]->get())); + make_result(result); } else { double result = std::pow(args[0]->get(), args[1]->get()); - result_ptr = std::make_shared(std::move(result)); - json_tmp_stack.push_back(result_ptr); + make_result(result); } - json_eval_stack.push(result_ptr.get()); } break; case Op::Modulo: { - auto args = get_arguments<2>(node); - result_ptr = std::make_shared(args[0]->get() % args[1]->get()); - json_tmp_stack.push_back(result_ptr); - json_eval_stack.push(result_ptr.get()); + const auto args = get_arguments<2>(node); + make_result(args[0]->get() % args[1]->get()); } break; case Op::AtId: { - json_eval_stack.pop(); // Pop id nullptr - auto container = get_arguments<1, false>(node)[0]; + const auto container = get_arguments<1, 0, false>(node)[0]; + node.arguments[1]->accept(*this); if (not_found_stack.empty()) { throw_renderer_error("could not find element with given name", node); } - auto id_node = not_found_stack.top(); + const auto id_node = not_found_stack.top(); not_found_stack.pop(); - json_eval_stack.push(&container->at(id_node->name)); + data_eval_stack.pop(); + data_eval_stack.push(&container->at(id_node->name)); } break; case Op::At: { - auto args = get_arguments<2>(node); - json_eval_stack.push(&args[0]->at(args[1]->get())); + const auto args = get_arguments<2>(node); + if (args[0]->is_object()) { + data_eval_stack.push(&args[0]->at(args[1]->get())); + } else { + data_eval_stack.push(&args[0]->at(args[1]->get())); + } } break; case Op::Default: { - auto default_arg = get_arguments<1>(node)[0]; - auto test_arg = get_arguments<1, false>(node)[0]; - json_eval_stack.push(test_arg ? test_arg : default_arg); + const auto test_arg = get_arguments<1, 0, false>(node)[0]; + data_eval_stack.push(test_arg ? test_arg : get_arguments<1, 1>(node)[0]); } break; case Op::DivisibleBy: { - auto args = get_arguments<2>(node); - int divisor = args[1]->get(); - result_ptr = std::make_shared((divisor != 0) && (args[0]->get() % divisor == 0)); - json_tmp_stack.push_back(result_ptr); - json_eval_stack.push(result_ptr.get()); + const auto args = get_arguments<2>(node); + const int divisor = args[1]->get(); + make_result((divisor != 0) && (args[0]->get() % divisor == 0)); } break; case Op::Even: { - result_ptr = std::make_shared(get_arguments<1>(node)[0]->get() % 2 == 0); - json_tmp_stack.push_back(result_ptr); - json_eval_stack.push(result_ptr.get()); + make_result(get_arguments<1>(node)[0]->get() % 2 == 0); } break; case Op::Exists: { - auto &&name = get_arguments<1>(node)[0]->get_ref(); - result_ptr = std::make_shared(json_input->contains(json::json_pointer(JsonNode::convert_dot_to_json_ptr(name)))); - json_tmp_stack.push_back(result_ptr); - json_eval_stack.push(result_ptr.get()); + auto&& name = get_arguments<1>(node)[0]->get_ref(); + make_result(data_input->contains(json::json_pointer(DataNode::convert_dot_to_ptr(name)))); } break; case Op::ExistsInObject: { - auto args = get_arguments<2>(node); - auto &&name = args[1]->get_ref(); - result_ptr = std::make_shared(args[0]->find(name) != args[0]->end()); - json_tmp_stack.push_back(result_ptr); - json_eval_stack.push(result_ptr.get()); + const auto args = get_arguments<2>(node); + auto&& name = args[1]->get_ref(); + make_result(args[0]->find(name) != args[0]->end()); } break; case Op::First: { - auto result = &get_arguments<1>(node)[0]->front(); - json_eval_stack.push(result); + const auto result = &get_arguments<1>(node)[0]->front(); + data_eval_stack.push(result); } break; case Op::Float: { - result_ptr = std::make_shared(std::stod(get_arguments<1>(node)[0]->get_ref())); - json_tmp_stack.push_back(result_ptr); - json_eval_stack.push(result_ptr.get()); + make_result(std::stod(get_arguments<1>(node)[0]->get_ref())); } break; case Op::Int: { - result_ptr = std::make_shared(std::stoi(get_arguments<1>(node)[0]->get_ref())); - json_tmp_stack.push_back(result_ptr); - json_eval_stack.push(result_ptr.get()); + make_result(std::stoi(get_arguments<1>(node)[0]->get_ref())); } break; case Op::Last: { - auto result = &get_arguments<1>(node)[0]->back(); - json_eval_stack.push(result); + const auto result = &get_arguments<1>(node)[0]->back(); + data_eval_stack.push(result); } break; case Op::Length: { - auto val = get_arguments<1>(node)[0]; + const auto val = get_arguments<1>(node)[0]; if (val->is_string()) { - result_ptr = std::make_shared(val->get_ref().length()); + make_result(val->get_ref().length()); } else { - result_ptr = std::make_shared(val->size()); + make_result(val->size()); } - json_tmp_stack.push_back(result_ptr); - json_eval_stack.push(result_ptr.get()); } break; case Op::Lower: { std::string result = get_arguments<1>(node)[0]->get(); std::transform(result.begin(), result.end(), result.begin(), ::tolower); - result_ptr = std::make_shared(std::move(result)); - json_tmp_stack.push_back(result_ptr); - json_eval_stack.push(result_ptr.get()); + make_result(std::move(result)); } break; case Op::Max: { - auto args = get_arguments<1>(node); - auto result = std::max_element(args[0]->begin(), args[0]->end()); - json_eval_stack.push(&(*result)); + const auto args = get_arguments<1>(node); + const auto result = std::max_element(args[0]->begin(), args[0]->end()); + data_eval_stack.push(&(*result)); } break; case Op::Min: { - auto args = get_arguments<1>(node); - auto result = std::min_element(args[0]->begin(), args[0]->end()); - json_eval_stack.push(&(*result)); + const auto args = get_arguments<1>(node); + const auto result = std::min_element(args[0]->begin(), args[0]->end()); + data_eval_stack.push(&(*result)); } break; case Op::Odd: { - result_ptr = std::make_shared(get_arguments<1>(node)[0]->get() % 2 != 0); - json_tmp_stack.push_back(result_ptr); - json_eval_stack.push(result_ptr.get()); + make_result(get_arguments<1>(node)[0]->get() % 2 != 0); } break; case Op::Range: { std::vector result(get_arguments<1>(node)[0]->get()); std::iota(result.begin(), result.end(), 0); - result_ptr = std::make_shared(std::move(result)); - json_tmp_stack.push_back(result_ptr); - json_eval_stack.push(result_ptr.get()); + make_result(std::move(result)); } break; case Op::Round: { - auto args = get_arguments<2>(node); - int precision = args[1]->get(); - double result = std::round(args[0]->get() * std::pow(10.0, precision)) / std::pow(10.0, precision); - result_ptr = std::make_shared(std::move(result)); - json_tmp_stack.push_back(result_ptr); - json_eval_stack.push(result_ptr.get()); + const auto args = get_arguments<2>(node); + const int precision = args[1]->get(); + const double result = std::round(args[0]->get() * std::pow(10.0, precision)) / std::pow(10.0, precision); + if (precision == 0) { + make_result(int(result)); + } else { + make_result(result); + } } break; case Op::Sort: { - result_ptr = std::make_shared(get_arguments<1>(node)[0]->get>()); + auto result_ptr = std::make_shared(get_arguments<1>(node)[0]->get>()); std::sort(result_ptr->begin(), result_ptr->end()); - json_tmp_stack.push_back(result_ptr); - json_eval_stack.push(result_ptr.get()); + data_tmp_stack.push_back(result_ptr); + data_eval_stack.push(result_ptr.get()); } break; case Op::Upper: { std::string result = get_arguments<1>(node)[0]->get(); std::transform(result.begin(), result.end(), result.begin(), ::toupper); - result_ptr = std::make_shared(std::move(result)); - json_tmp_stack.push_back(result_ptr); - json_eval_stack.push(result_ptr.get()); + make_result(std::move(result)); } break; case Op::IsBoolean: { - result_ptr = std::make_shared(get_arguments<1>(node)[0]->is_boolean()); - json_tmp_stack.push_back(result_ptr); - json_eval_stack.push(result_ptr.get()); + make_result(get_arguments<1>(node)[0]->is_boolean()); } break; case Op::IsNumber: { - result_ptr = std::make_shared(get_arguments<1>(node)[0]->is_number()); - json_tmp_stack.push_back(result_ptr); - json_eval_stack.push(result_ptr.get()); + make_result(get_arguments<1>(node)[0]->is_number()); } break; case Op::IsInteger: { - result_ptr = std::make_shared(get_arguments<1>(node)[0]->is_number_integer()); - json_tmp_stack.push_back(result_ptr); - json_eval_stack.push(result_ptr.get()); + make_result(get_arguments<1>(node)[0]->is_number_integer()); } break; case Op::IsFloat: { - result_ptr = std::make_shared(get_arguments<1>(node)[0]->is_number_float()); - json_tmp_stack.push_back(result_ptr); - json_eval_stack.push(result_ptr.get()); + make_result(get_arguments<1>(node)[0]->is_number_float()); } break; case Op::IsObject: { - result_ptr = std::make_shared(get_arguments<1>(node)[0]->is_object()); - json_tmp_stack.push_back(result_ptr); - json_eval_stack.push(result_ptr.get()); + make_result(get_arguments<1>(node)[0]->is_object()); } break; case Op::IsArray: { - result_ptr = std::make_shared(get_arguments<1>(node)[0]->is_array()); - json_tmp_stack.push_back(result_ptr); - json_eval_stack.push(result_ptr.get()); + make_result(get_arguments<1>(node)[0]->is_array()); } break; case Op::IsString: { - result_ptr = std::make_shared(get_arguments<1>(node)[0]->is_string()); - json_tmp_stack.push_back(result_ptr); - json_eval_stack.push(result_ptr.get()); + make_result(get_arguments<1>(node)[0]->is_string()); } break; case Op::Callback: { - auto args = get_argument_vector(node.number_args, node); - result_ptr = std::make_shared(node.callback(args)); - json_tmp_stack.push_back(result_ptr); - json_eval_stack.push(result_ptr.get()); + auto args = get_argument_vector(node); + make_result(node.callback(args)); + } break; + case Op::Super: { + const auto args = get_argument_vector(node); + const size_t old_level = current_level; + const size_t level_diff = (args.size() == 1) ? args[0]->get() : 1; + const size_t level = current_level + level_diff; + + if (block_statement_stack.empty()) { + throw_renderer_error("super() call is not within a block", node); + } + + if (level < 1 || level > template_stack.size() - 1) { + throw_renderer_error("level of super() call does not match parent templates (between 1 and " + std::to_string(template_stack.size() - 1) + ")", node); + } + + const auto current_block_statement = block_statement_stack.back(); + const Template* new_template = template_stack.at(level); + const Template* old_template = current_template; + const auto block_it = new_template->block_storage.find(current_block_statement->name); + if (block_it != new_template->block_storage.end()) { + current_template = new_template; + current_level = level; + block_it->second->block.accept(*this); + current_level = old_level; + current_template = old_template; + } else { + throw_renderer_error("could not find block with name '" + current_block_statement->name + "'", node); + } + make_result(nullptr); + } break; + case Op::Join: { + const auto args = get_arguments<2>(node); + const auto separator = args[1]->get(); + std::ostringstream os; + std::string sep; + for (const auto& value : *args[0]) { + os << sep; + if (value.is_string()) { + os << value.get(); // otherwise the value is surrounded with "" + } else { + os << value; + } + sep = separator; + } + make_result(os.str()); } break; case Op::ParenLeft: case Op::ParenRight: @@ -3803,15 +2552,15 @@ class Renderer : public NodeVisitor { } void visit(const ExpressionListNode& node) { - print_json(eval_expression_list(node)); + print_data(eval_expression_list(node)); } - void visit(const StatementNode&) { } + void visit(const StatementNode&) {} - void visit(const ForStatementNode&) { } + void visit(const ForStatementNode&) {} void visit(const ForArrayStatementNode& node) { - auto result = eval_expression_list(node.condition); + const auto result = eval_expression_list(node.condition); if (!result->is_array()) { throw_renderer_error("object must be an array", node); } @@ -3825,7 +2574,7 @@ class Renderer : public NodeVisitor { (*current_loop_data)["is_first"] = true; (*current_loop_data)["is_last"] = (result->size() <= 1); for (auto it = result->begin(); it != result->end(); ++it) { - json_additional_data[static_cast(node.value)] = *it; + additional_data[static_cast(node.value)] = *it; (*current_loop_data)["index"] = index; (*current_loop_data)["index1"] = index + 1; @@ -3840,17 +2589,17 @@ class Renderer : public NodeVisitor { ++index; } - json_additional_data[static_cast(node.value)].clear(); + additional_data[static_cast(node.value)].clear(); if (!(*current_loop_data)["parent"].empty()) { - auto tmp = (*current_loop_data)["parent"]; + const auto tmp = (*current_loop_data)["parent"]; *current_loop_data = std::move(tmp); } else { - current_loop_data = &json_additional_data["loop"]; + current_loop_data = &additional_data["loop"]; } } void visit(const ForObjectStatementNode& node) { - auto result = eval_expression_list(node.condition); + const auto result = eval_expression_list(node.condition); if (!result->is_object()) { throw_renderer_error("object must be an object", node); } @@ -3863,8 +2612,8 @@ class Renderer : public NodeVisitor { (*current_loop_data)["is_first"] = true; (*current_loop_data)["is_last"] = (result->size() <= 1); for (auto it = result->begin(); it != result->end(); ++it) { - json_additional_data[static_cast(node.key)] = it.key(); - json_additional_data[static_cast(node.value)] = it.value(); + additional_data[static_cast(node.key)] = it.key(); + additional_data[static_cast(node.value)] = it.value(); (*current_loop_data)["index"] = index; (*current_loop_data)["index1"] = index + 1; @@ -3879,17 +2628,17 @@ class Renderer : public NodeVisitor { ++index; } - json_additional_data[static_cast(node.key)].clear(); - json_additional_data[static_cast(node.value)].clear(); + additional_data[static_cast(node.key)].clear(); + additional_data[static_cast(node.value)].clear(); if (!(*current_loop_data)["parent"].empty()) { *current_loop_data = std::move((*current_loop_data)["parent"]); } else { - current_loop_data = &json_additional_data["loop"]; + current_loop_data = &additional_data["loop"]; } } void visit(const IfStatementNode& node) { - auto result = eval_expression_list(node.condition); + const auto result = eval_expression_list(node.condition); if (truthy(result.get())) { node.true_statement.accept(*this); } else if (node.has_false_statement) { @@ -3899,35 +2648,63 @@ class Renderer : public NodeVisitor { void visit(const IncludeStatementNode& node) { auto sub_renderer = Renderer(config, template_storage, function_storage); - auto included_template_it = template_storage.find(node.file); - + const auto included_template_it = template_storage.find(node.file); if (included_template_it != template_storage.end()) { - sub_renderer.render_to(*output_stream, included_template_it->second, *json_input, &json_additional_data); + sub_renderer.render_to(*output_stream, included_template_it->second, *data_input, &additional_data); } else if (config.throw_at_missing_includes) { throw_renderer_error("include '" + node.file + "' not found", node); } } + void visit(const ExtendsStatementNode& node) { + const auto included_template_it = template_storage.find(node.file); + if (included_template_it != template_storage.end()) { + const Template* parent_template = &included_template_it->second; + render_to(*output_stream, *parent_template, *data_input, &additional_data); + break_rendering = true; + } else if (config.throw_at_missing_includes) { + throw_renderer_error("extends '" + node.file + "' not found", node); + } + } + + void visit(const BlockStatementNode& node) { + const size_t old_level = current_level; + current_level = 0; + current_template = template_stack.front(); + const auto block_it = current_template->block_storage.find(node.name); + if (block_it != current_template->block_storage.end()) { + block_statement_stack.emplace_back(&node); + block_it->second->block.accept(*this); + block_statement_stack.pop_back(); + } + current_level = old_level; + current_template = template_stack.back(); + } + void visit(const SetStatementNode& node) { - json_additional_data[node.key] = *eval_expression_list(node.expression); + std::string ptr = node.key; + replace_substring(ptr, ".", "/"); + ptr = "/" + ptr; + additional_data[json::json_pointer(ptr)] = *eval_expression_list(node.expression); } public: - Renderer(const RenderConfig& config, const TemplateStorage &template_storage, const FunctionStorage &function_storage) - : config(config), template_storage(template_storage), function_storage(function_storage) { } + Renderer(const RenderConfig& config, const TemplateStorage& template_storage, const FunctionStorage& function_storage) + : config(config), template_storage(template_storage), function_storage(function_storage) {} - void render_to(std::ostream &os, const Template &tmpl, const json &data, json *loop_data = nullptr) { + void render_to(std::ostream& os, const Template& tmpl, const json& data, json* loop_data = nullptr) { output_stream = &os; current_template = &tmpl; - json_input = &data; + data_input = &data; if (loop_data) { - json_additional_data = *loop_data; - current_loop_data = &json_additional_data["loop"]; + additional_data = *loop_data; + current_loop_data = &additional_data["loop"]; } + template_stack.emplace_back(current_template); current_template->root.accept(*this); - json_tmp_stack.clear(); + data_tmp_stack.clear(); } }; @@ -3935,8 +2712,6 @@ public: #endif // INCLUDE_INJA_RENDERER_HPP_ -// #include "string_view.hpp" - // #include "template.hpp" // #include "utils.hpp" @@ -3944,8 +2719,6 @@ public: namespace inja { -using json = nlohmann::json; - /*! * \brief Class for changing the configuration. */ @@ -3961,15 +2734,14 @@ class Environment { TemplateStorage template_storage; public: - Environment() : Environment("") {} + Environment(): Environment("") {} - explicit Environment(const std::string &global_path) : input_path(global_path), output_path(global_path) {} + explicit Environment(const std::string& global_path): input_path(global_path), output_path(global_path) {} - Environment(const std::string &input_path, const std::string &output_path) - : input_path(input_path), output_path(output_path) {} + Environment(const std::string& input_path, const std::string& output_path): input_path(input_path), output_path(output_path) {} /// Sets the opener and closer for template statements - void set_statement(const std::string &open, const std::string &close) { + void set_statement(const std::string& open, const std::string& close) { lexer_config.statement_open = open; lexer_config.statement_open_no_lstrip = open + "+"; lexer_config.statement_open_force_lstrip = open + "-"; @@ -3979,13 +2751,13 @@ public: } /// Sets the opener for template line statements - void set_line_statement(const std::string &open) { + void set_line_statement(const std::string& open) { lexer_config.line_statement = open; lexer_config.update_open_chars(); } /// Sets the opener and closer for template expressions - void set_expression(const std::string &open, const std::string &close) { + void set_expression(const std::string& open, const std::string& close) { lexer_config.expression_open = open; lexer_config.expression_open_force_lstrip = open + "-"; lexer_config.expression_close = close; @@ -3994,9 +2766,11 @@ public: } /// Sets the opener and closer for template comments - void set_comment(const std::string &open, const std::string &close) { + void set_comment(const std::string& open, const std::string& close) { lexer_config.comment_open = open; + lexer_config.comment_open_force_lstrip = open + "-"; lexer_config.comment_close = close; + lexer_config.comment_close_force_rstrip = "-" + close; lexer_config.update_open_chars(); } @@ -4020,75 +2794,79 @@ public: render_config.throw_at_missing_includes = will_throw; } - Template parse(nonstd::string_view input) { + Template parse(std::string_view input) { Parser parser(parser_config, lexer_config, template_storage, function_storage); return parser.parse(input); } - Template parse_template(const std::string &filename) { + Template parse_template(const std::string& filename) { Parser parser(parser_config, lexer_config, template_storage, function_storage); auto result = Template(parser.load_file(input_path + static_cast(filename))); parser.parse_into_template(result, input_path + static_cast(filename)); return result; } - Template parse_file(const std::string &filename) { + Template parse_file(const std::string& filename) { return parse_template(filename); } - std::string render(nonstd::string_view input, const json &data) { return render(parse(input), data); } + std::string render(std::string_view input, const json& data) { + return render(parse(input), data); + } - std::string render(const Template &tmpl, const json &data) { + std::string render(const Template& tmpl, const json& data) { std::stringstream os; render_to(os, tmpl, data); return os.str(); } - std::string render_file(const std::string &filename, const json &data) { + std::string render_file(const std::string& filename, const json& data) { return render(parse_template(filename), data); } - std::string render_file_with_json_file(const std::string &filename, const std::string &filename_data) { + std::string render_file_with_json_file(const std::string& filename, const std::string& filename_data) { const json data = load_json(filename_data); return render_file(filename, data); } - void write(const std::string &filename, const json &data, const std::string &filename_out) { + void write(const std::string& filename, const json& data, const std::string& filename_out) { std::ofstream file(output_path + filename_out); file << render_file(filename, data); file.close(); } - void write(const Template &temp, const json &data, const std::string &filename_out) { + void write(const Template& temp, const json& data, const std::string& filename_out) { std::ofstream file(output_path + filename_out); file << render(temp, data); file.close(); } - void write_with_json_file(const std::string &filename, const std::string &filename_data, - const std::string &filename_out) { + void write_with_json_file(const std::string& filename, const std::string& filename_data, const std::string& filename_out) { const json data = load_json(filename_data); write(filename, data, filename_out); } - void write_with_json_file(const Template &temp, const std::string &filename_data, const std::string &filename_out) { + void write_with_json_file(const Template& temp, const std::string& filename_data, const std::string& filename_out) { const json data = load_json(filename_data); write(temp, data, filename_out); } - std::ostream &render_to(std::ostream &os, const Template &tmpl, const json &data) { + std::ostream& render_to(std::ostream& os, const Template& tmpl, const json& data) { Renderer(render_config, template_storage, function_storage).render_to(os, tmpl, data); return os; } - std::string load_file(const std::string &filename) { + std::string load_file(const std::string& filename) { Parser parser(parser_config, lexer_config, template_storage, function_storage); return parser.load_file(input_path + filename); } - json load_json(const std::string &filename) { + json load_json(const std::string& filename) { std::ifstream file; - open_file_or_throw(input_path + filename, file); + file.open(input_path + filename); + if (file.fail()) { + INJA_THROW(FileError("failed accessing file at '" + input_path + filename + "'")); + } json j; file >> j; return j; @@ -4097,51 +2875,61 @@ public: /*! @brief Adds a variadic callback */ - void add_callback(const std::string &name, const CallbackFunction &callback) { + void add_callback(const std::string& name, const CallbackFunction& callback) { add_callback(name, -1, callback); } /*! @brief Adds a variadic void callback */ - void add_void_callback(const std::string &name, const VoidCallbackFunction &callback) { + void add_void_callback(const std::string& name, const VoidCallbackFunction& callback) { add_void_callback(name, -1, callback); } /*! @brief Adds a callback with given number or arguments */ - void add_callback(const std::string &name, int num_args, const CallbackFunction &callback) { + void add_callback(const std::string& name, int num_args, const CallbackFunction& callback) { function_storage.add_callback(name, num_args, callback); } /*! @brief Adds a void callback with given number or arguments */ - void add_void_callback(const std::string &name, int num_args, const VoidCallbackFunction &callback) { - function_storage.add_callback(name, num_args, [callback](Arguments& args) { callback(args); return json(); }); + void add_void_callback(const std::string& name, int num_args, const VoidCallbackFunction& callback) { + function_storage.add_callback(name, num_args, [callback](Arguments& args) { + callback(args); + return json(); + }); } /** Includes a template with a given name into the environment. * Then, a template can be rendered in another template using the * include "" syntax. */ - void include_template(const std::string &name, const Template &tmpl) { + void include_template(const std::string& name, const Template& tmpl) { template_storage[name] = tmpl; } + + /*! + @brief Sets a function that is called when an included file is not found + */ + void set_include_callback(const std::function& callback) { + parser_config.include_callback = callback; + } }; /*! @brief render with default settings to a string */ -inline std::string render(nonstd::string_view input, const json &data) { +inline std::string render(std::string_view input, const json& data) { return Environment().render(input, data); } /*! @brief render with default settings to the given output stream */ -inline void render_to(std::ostream &os, nonstd::string_view input, const json &data) { +inline void render_to(std::ostream& os, std::string_view input, const json& data) { Environment env; env.render_to(os, env.parse(input), data); } @@ -4156,8 +2944,6 @@ inline void render_to(std::ostream &os, nonstd::string_view input, const json &d // #include "renderer.hpp" -// #include "string_view.hpp" - // #include "template.hpp" diff --git a/test/benchmark.cpp b/test/benchmark.cpp index 571a3ed..92c696f 100644 --- a/test/benchmark.cpp +++ b/test/benchmark.cpp @@ -3,22 +3,24 @@ #include "hayai/hayai.hpp" #include -using json = nlohmann::json; - inja::Environment env; const std::string test_file_directory {"../test/data/benchmark/"}; -json small_data = env.load_json(test_file_directory + "small_data.json"); -json large_data = env.load_json(test_file_directory + "large_data.json"); +auto small_data = env.load_json(test_file_directory + "small_data.json"); +auto large_data = env.load_json(test_file_directory + "large_data.json"); std::string medium_template = env.load_file(test_file_directory + "medium_template.txt"); std::string large_template = env.load_file(test_file_directory + "large_template.txt"); - -BENCHMARK(SmallDataMediumTemplate, render, 5, 30) { env.render(medium_template, small_data); } -BENCHMARK(LargeDataMediumTemplate, render, 5, 15) { env.render(medium_template, large_data); } -BENCHMARK(LargeDataLargeTemplate, render, 5, 5) { env.render(large_template, large_data); } - +BENCHMARK(SmallDataMediumTemplate, render, 5, 30) { + env.render(medium_template, small_data); +} +BENCHMARK(LargeDataMediumTemplate, render, 5, 15) { + env.render(medium_template, large_data); +} +BENCHMARK(LargeDataLargeTemplate, render, 5, 5) { + env.render(large_template, large_data); +} int main() { hayai::ConsoleOutputter consoleOutputter; diff --git a/test/data/html-extend/base.txt b/test/data/html-extend/base.txt new file mode 100644 index 0000000..e301c7a --- /dev/null +++ b/test/data/html-extend/base.txt @@ -0,0 +1,11 @@ + + + + {% block head -%} + {% block title %}Default - {% endblock %} + {%- endblock -%} + + + {% block body %}ignored{% endblock %} + + diff --git a/test/data/html-extend/data.json b/test/data/html-extend/data.json new file mode 100644 index 0000000..c5726a5 --- /dev/null +++ b/test/data/html-extend/data.json @@ -0,0 +1,12 @@ +{ + "author": "Pantor", + "date": "23/12/2018", + "tags": [ + "test", + "templates" + ], + "views": 123, + "title": "Inja works.", + "content": "Inja is the best and fastest template engine for C++. Period.", + "footer-text": "This is the footer." +} diff --git a/test/data/html-extend/inter.txt b/test/data/html-extend/inter.txt new file mode 100644 index 0000000..d20d97b --- /dev/null +++ b/test/data/html-extend/inter.txt @@ -0,0 +1,3 @@ +{% extends "base.txt" %} +{% block title %}Inter {{ author }}{% endblock %} +{% block body %}
Intermediate Content
{% endblock %} \ No newline at end of file diff --git a/test/data/html-extend/result.txt b/test/data/html-extend/result.txt new file mode 100644 index 0000000..6d99e29 --- /dev/null +++ b/test/data/html-extend/result.txt @@ -0,0 +1,11 @@ + + + + Default - Inter Pantor: Inja works. + + + + +
Inja is the best and fastest template engine for C++. Period.
+ + diff --git a/test/data/html-extend/template.txt b/test/data/html-extend/template.txt new file mode 100644 index 0000000..18eeb5d --- /dev/null +++ b/test/data/html-extend/template.txt @@ -0,0 +1,7 @@ +{% extends "inter.txt" %} +{% block head -%} + {{ super(2) }} + +{%- endblock %} +{% block title %}{{ super(2) }}{{ super() }}: {{ title }}{% endblock %} +{% block body %}
{{ content }}
{% endblock %} diff --git a/test/data/include-both.txt b/test/data/include-both.txt new file mode 100755 index 0000000..cbdc374 --- /dev/null +++ b/test/data/include-both.txt @@ -0,0 +1 @@ +{% include "simple.txt" %} - {% include "body" %} \ No newline at end of file diff --git a/test/jinja.py b/test/jinja.py new file mode 100644 index 0000000..94bc24c --- /dev/null +++ b/test/jinja.py @@ -0,0 +1,11 @@ +from jinja2 import Environment, PackageLoader, select_autoescape + + +if __name__ == '__main__': + env = Environment( + loader=PackageLoader("jinja"), + autoescape=select_autoescape() + ) + + template = env.get_template("example.txt") + print(template.render(name="Jeff")) diff --git a/test/templates/example.txt b/test/templates/example.txt new file mode 100644 index 0000000..655a3c9 --- /dev/null +++ b/test/templates/example.txt @@ -0,0 +1 @@ +Hello {{- name -}} ! \ No newline at end of file diff --git a/test/test-files.cpp b/test/test-files.cpp index 74d099a..aa01cff 100644 --- a/test/test-files.cpp +++ b/test/test-files.cpp @@ -2,10 +2,12 @@ TEST_CASE("loading") { inja::Environment env; - json data; + inja::json data; data["name"] = "Jeff"; - SUBCASE("Files should be loaded") { CHECK(env.load_file(test_file_directory + "simple.txt") == "Hello {{ name }}."); } + SUBCASE("Files should be loaded") { + CHECK(env.load_file(test_file_directory + "simple.txt") == "Hello {{ name }}."); + } SUBCASE("Files should be rendered") { CHECK(env.render_file(test_file_directory + "simple.txt", data) == "Hello Jeff."); @@ -27,10 +29,9 @@ TEST_CASE("loading") { TEST_CASE("complete-files") { inja::Environment env {test_file_directory}; - for (std::string test_name : {"simple-file", "nested", "nested-line", "html"}) { + for (std::string test_name : {"simple-file", "nested", "nested-line", "html", "html-extend"}) { SUBCASE(test_name.c_str()) { - CHECK(env.render_file_with_json_file(test_name + "/template.txt", test_name + "/data.json") == - env.load_file(test_name + "/result.txt")); + CHECK(env.render_file_with_json_file(test_name + "/template.txt", test_name + "/data.json") == env.load_file(test_name + "/result.txt")); } } @@ -49,8 +50,7 @@ TEST_CASE("complete-files-whitespace-control") { for (std::string test_name : {"nested-whitespace"}) { SUBCASE(test_name.c_str()) { - CHECK(env.render_file_with_json_file(test_name + "/template.txt", test_name + "/data.json") == - env.load_file(test_name + "/result.txt")); + CHECK(env.render_file_with_json_file(test_name + "/template.txt", test_name + "/data.json") == env.load_file(test_name + "/result.txt")); } } } @@ -58,7 +58,7 @@ TEST_CASE("complete-files-whitespace-control") { TEST_CASE("global-path") { inja::Environment env {test_file_directory, "./"}; inja::Environment env_result {"./"}; - json data; + inja::json data; data["name"] = "Jeff"; SUBCASE("Files should be written") { @@ -73,7 +73,20 @@ TEST_CASE("include-without-local-files") { inja::Environment env {test_file_directory}; env.set_search_included_templates_in_files(false); - SUBCASE("html") { - CHECK_THROWS_WITH(env.render_file_with_json_file("html/template.txt", "html/data.json"), "[inja.exception.render_error] (at 3:14) include '../test/data/html/header.txt' not found"); - } + CHECK_THROWS_WITH(env.render_file_with_json_file("html/template.txt", "html/data.json"), + "[inja.exception.render_error] (at 3:14) include 'header.txt' not found"); +} + +TEST_CASE("include-in-memory-and-file-template") { + inja::Environment env {test_file_directory}; + + inja::json data; + data["name"] = "Jeff"; + + CHECK_THROWS_WITH(env.render_file("include-both.txt", data), "[inja.exception.file_error] failed accessing file at '../test/data/body'"); + + const auto parsed_body_template = env.parse("Bye {{ name }}."); + env.include_template("body", parsed_body_template); + + CHECK(env.render_file("include-both.txt", data) == "Hello Jeff. - Bye Jeff."); } diff --git a/test/test-functions.cpp b/test/test-functions.cpp index 4870205..667f553 100644 --- a/test/test-functions.cpp +++ b/test/test-functions.cpp @@ -3,7 +3,7 @@ TEST_CASE("functions") { inja::Environment env; - json data; + inja::json data; data["name"] = "Peter"; data["city"] = "New York"; data["names"] = {"Jeff", "Seb", "Peter", "Tom"}; @@ -56,8 +56,8 @@ TEST_CASE("functions") { SUBCASE("length") { CHECK(env.render("{{ length(names) }}", data) == "4"); // Length of array CHECK(env.render("{{ length(name) }}", data) == "5"); // Length of string - // CHECK_THROWS_WITH( env.render("{{ length(5) }}", data), "[inja.exception.json_error] - // [json.exception.type_error.302] type must be array, but is number" ); + // CHECK_THROWS_WITH( env.render("{{ length(5) }}", data), "[inja.exception.json_error] + // [json.exception.type_error.302] type must be array, but is number" ); } SUBCASE("sort") { @@ -70,6 +70,8 @@ TEST_CASE("functions") { SUBCASE("at") { CHECK(env.render("{{ at(names, 0) }}", data) == "Jeff"); CHECK(env.render("{{ at(names, i) }}", data) == "Seb"); + CHECK(env.render("{{ at(brother, \"name\") }}", data) == "Chris"); + CHECK(env.render("{{ at(at(brother, \"daughters\"), 0) }}", data) == "Maria"); // CHECK(env.render("{{ at(names, 45) }}", data) == "Jeff"); } @@ -86,7 +88,7 @@ TEST_CASE("functions") { } SUBCASE("round") { - CHECK(env.render("{{ round(4, 0) }}", data) == "4.0"); + CHECK(env.render("{{ round(4, 0) }}", data) == "4"); CHECK(env.render("{{ round(temperature, 2) }}", data) == "25.68"); // CHECK_THROWS_WITH( env.render("{{ round(name, 2) }}", data), "[inja.exception.json_error] // [json.exception.type_error.302] type must be number, but is string" ); @@ -148,8 +150,7 @@ TEST_CASE("functions") { CHECK(env.render("{{ default(name, \"nobody\") }}", data) == "Peter"); CHECK(env.render("{{ default(surname, \"nobody\") }}", data) == "nobody"); CHECK(env.render("{{ default(surname, \"{{ surname }}\") }}", data) == "{{ surname }}"); - CHECK_THROWS_WITH(env.render("{{ default(surname, lastname) }}", data), - "[inja.exception.render_error] (at 1:21) variable 'lastname' not found"); + CHECK_THROWS_WITH(env.render("{{ default(surname, lastname) }}", data), "[inja.exception.render_error] (at 1:21) variable 'lastname' not found"); } SUBCASE("exists") { @@ -166,10 +167,13 @@ TEST_CASE("functions") { CHECK(env.render("{{ existsIn(brother, \"parents\") }}", data) == "false"); CHECK(env.render("{{ existsIn(brother, property) }}", data) == "true"); CHECK(env.render("{{ existsIn(brother, name) }}", data) == "false"); - CHECK_THROWS_WITH(env.render("{{ existsIn(sister, \"lastname\") }}", data), - "[inja.exception.render_error] (at 1:13) variable 'sister' not found"); - CHECK_THROWS_WITH(env.render("{{ existsIn(brother, sister) }}", data), - "[inja.exception.render_error] (at 1:22) variable 'sister' not found"); + CHECK_THROWS_WITH(env.render("{{ existsIn(sister, \"lastname\") }}", data), "[inja.exception.render_error] (at 1:13) variable 'sister' not found"); + CHECK_THROWS_WITH(env.render("{{ existsIn(brother, sister) }}", data), "[inja.exception.render_error] (at 1:22) variable 'sister' not found"); + } + + SUBCASE("join") { + CHECK(env.render("{{ join(names, \" | \") }}", data) == "Jeff | Seb | Peter | Tom"); + CHECK(env.render("{{ join(vars, \", \") }}", data) == "2, 3, 4, 0, -1, -2, -3"); } SUBCASE("isType") { @@ -190,12 +194,22 @@ TEST_CASE("functions") { } } +TEST_CASE("assignments") { + inja::Environment env; + inja::json data; + data["age"] = 28; + + CHECK(env.render("{% set new_hour=23 %}{{ new_hour }}", data) == "23"); + CHECK(env.render("{% set time.start=18 %}{{ time.start }}pm", data) == "18pm"); + CHECK(env.render("{% set v1 = \"a\" %}{% set v2 = \"b\" %}{% set var = v1 + v2 %}{{ var }}", data) == "ab"); +} + TEST_CASE("callbacks") { inja::Environment env; - json data; + inja::json data; data["age"] = 28; - env.add_callback("double", 1, [](inja::Arguments &args) { + env.add_callback("double", 1, [](inja::Arguments& args) { int number = args.at(0)->get(); return 2 * number; }); @@ -206,7 +220,7 @@ TEST_CASE("callbacks") { }); std::string greet = "Hello"; - env.add_callback("double-greetings", 0, [greet](inja::Arguments args) { return greet + " " + greet + "!"; }); + env.add_callback("double-greetings", 0, [greet](inja::Arguments) { return greet + " " + greet + "!"; }); env.add_callback("multiply", 2, [](inja::Arguments args) { double number1 = args.at(0)->get(); @@ -226,11 +240,11 @@ TEST_CASE("callbacks") { return number1.length(); }); - env.add_void_callback("log", 1, [](inja::Arguments args) { - int a = 2; + env.add_void_callback("log", 1, [](inja::Arguments) { + }); - env.add_callback("multiply", 0, [](inja::Arguments args) { return 1.0; }); + env.add_callback("multiply", 0, [](inja::Arguments) { return 1.0; }); CHECK(env.render("{{ double(age) }}", data) == "56"); CHECK(env.render("{{ half(age) }}", data) == "14"); @@ -245,7 +259,7 @@ TEST_CASE("callbacks") { SUBCASE("Variadic") { env.add_callback("argmax", [](inja::Arguments& args) { - auto result = std::max_element(args.begin(), args.end(), [](const json* a, const json* b) { return *a < *b;}); + auto result = std::max_element(args.begin(), args.end(), [](const inja::json* a, const inja::json* b) { return *a < *b; }); return std::distance(args.begin(), result); }); @@ -256,7 +270,7 @@ TEST_CASE("callbacks") { TEST_CASE("combinations") { inja::Environment env; - json data; + inja::json data; data["name"] = "Peter"; data["city"] = "Brunswick"; data["age"] = 29; @@ -282,4 +296,5 @@ TEST_CASE("combinations") { CHECK(env.render("{{ not true }}", data) == "false"); CHECK(env.render("{{ not (true) }}", data) == "false"); CHECK(env.render("{{ true or (true or true) }}", data) == "true"); + CHECK(env.render("{{ at(list_of_objects, 1).b }}", data) == "3"); } diff --git a/test/test-renderer.cpp b/test/test-renderer.cpp index fe38738..2089791 100644 --- a/test/test-renderer.cpp +++ b/test/test-renderer.cpp @@ -2,7 +2,7 @@ TEST_CASE("types") { inja::Environment env; - json data; + inja::json data; data["name"] = "Peter"; data["city"] = "Brunswick"; data["age"] = 29; @@ -50,31 +50,26 @@ TEST_CASE("types") { SUBCASE("loops") { CHECK(env.render("{% for name in names %}a{% endfor %}", data) == "aa"); CHECK(env.render("Hello {% for name in names %}{{ name }} {% endfor %}!", data) == "Hello Jeff Seb !"); - CHECK(env.render("Hello {% for name in names %}{{ loop.index }}: {{ name }}, {% endfor %}!", data) == - "Hello 0: Jeff, 1: Seb, !"); + CHECK(env.render("Hello {% for name in names %}{{ loop.index }}: {{ name }}, {% endfor %}!", data) == "Hello 0: Jeff, 1: Seb, !"); CHECK(env.render("{% for type, name in relatives %}{{ loop.index1 }}: {{ type }}: {{ name }}{% if loop.is_last == " "false %}, {% endif %}{% endfor %}", data) == "1: brother: Chris, 2: mother: Maria, 3: sister: Jenny"); CHECK(env.render("{% for v in vars %}{% if v > 0 %}+{% endif %}{% endfor %}", data) == "+++"); - CHECK(env.render( - "{% for name in names %}{{ loop.index }}: {{ name }}{% if not loop.is_last %}, {% endif %}{% endfor %}!", - data) == "0: Jeff, 1: Seb!"); + CHECK(env.render("{% for name in names %}{{ loop.index }}: {{ name }}{% if not loop.is_last %}, {% endif %}{% endfor %}!", data) == "0: Jeff, 1: Seb!"); CHECK(env.render("{% for name in names %}{{ loop.index }}: {{ name }}{% if loop.is_last == false %}, {% endif %}{% " "endfor %}!", data) == "0: Jeff, 1: Seb!"); CHECK(env.render("{% for name in [] %}a{% endfor %}", data) == ""); - CHECK_THROWS_WITH(env.render("{% for name ins names %}a{% endfor %}", data), - "[inja.exception.parser_error] (at 1:13) expected 'in', got 'ins'"); - CHECK_THROWS_WITH(env.render("{% for name in empty_loop %}a{% endfor %}", data), - "[inja.exception.render_error] (at 1:16) variable 'empty_loop' not found"); + CHECK_THROWS_WITH(env.render("{% for name ins names %}a{% endfor %}", data), "[inja.exception.parser_error] (at 1:13) expected 'in', got 'ins'"); + CHECK_THROWS_WITH(env.render("{% for name in empty_loop %}a{% endfor %}", data), "[inja.exception.render_error] (at 1:16) variable 'empty_loop' not found"); // CHECK_THROWS_WITH( env.render("{% for name in relatives %}{{ name }}{% endfor %}", data), // "[inja.exception.json_error] [json.exception.type_error.302] type must be array, but is object" ); } SUBCASE("nested loops") { - auto ldata = json::parse(R""""( + auto ldata = inja::json::parse(R""""( { "outer" : [ { "inner" : [ { "in2" : [ 1, 2 ] }, @@ -112,12 +107,10 @@ TEST_CASE("types") { CHECK(env.render("{% if age >= 30 %}Right{% else %}Wrong{% endif %}", data) == "Wrong"); CHECK(env.render("{% if age in [28, 29, 30] %}True{% endif %}", data) == "True"); CHECK(env.render("{% if age == 28 %}28{% else if age == 29 %}29{% endif %}", data) == "29"); - CHECK(env.render("{% if age == 26 %}26{% else if age == 27 %}27{% else if age == 28 %}28{% else %}29{% endif %}", - data) == "29"); + CHECK(env.render("{% if age == 26 %}26{% else if age == 27 %}27{% else if age == 28 %}28{% else %}29{% endif %}", data) == "29"); CHECK(env.render("{% if age == 25 %}+{% endif %}{% if age == 29 %}+{% else %}-{% endif %}", data) == "+"); - CHECK_THROWS_WITH(env.render("{% if is_happy %}{% if is_happy %}{% endif %}", data), - "[inja.exception.parser_error] (at 1:46) unmatched if"); + CHECK_THROWS_WITH(env.render("{% if is_happy %}{% if is_happy %}{% endif %}", data), "[inja.exception.parser_error] (at 1:46) unmatched if"); CHECK_THROWS_WITH(env.render("{% if is_happy %}{% else if is_happy %}{% end if %}", data), "[inja.exception.parser_error] (at 1:43) expected statement, got 'end'"); } @@ -125,8 +118,18 @@ TEST_CASE("types") { SUBCASE("set statements") { CHECK(env.render("{% set predefined=true %}{% if predefined %}a{% endif %}", data) == "a"); CHECK(env.render("{% set predefined=false %}{% if predefined %}a{% endif %}", data) == ""); - CHECK_THROWS_WITH(env.render("{% if predefined %}{% endif %}", data), - "[inja.exception.render_error] (at 1:7) variable 'predefined' not found"); + CHECK(env.render("{% set age=30 %}{{age}}", data) == "30"); + CHECK(env.render("{% set predefined.value=1 %}{% if existsIn(predefined, \"value\") %}{{predefined.value}}{% endif %}", data) == "1"); + CHECK(env.render("{% set brother.name=\"Bob\" %}{{brother.name}}", data) == "Bob"); + CHECK_THROWS_WITH(env.render("{% if predefined %}{% endif %}", data), "[inja.exception.render_error] (at 1:7) variable 'predefined' not found"); + CHECK(env.render("{{age}}", data) == "29"); + CHECK(env.render("{{brother.name}}", data) == "Chris"); + } + + SUBCASE("short circuit evaluation") { + CHECK(env.render("{% if 0 and undefined %}do{% else %}nothing{% endif %}", data) == "nothing"); + CHECK_THROWS_WITH(env.render("{% if 1 and undefined %}do{% else %}nothing{% endif %}", data), + "[inja.exception.render_error] (at 1:13) variable 'undefined' not found"); } SUBCASE("line statements") { @@ -147,7 +150,7 @@ Yeah! } TEST_CASE("templates") { - json data; + inja::json data; data["name"] = "Peter"; data["city"] = "Brunswick"; data["is_happy"] = true; @@ -170,19 +173,37 @@ TEST_CASE("templates") { inja::Template t2 = env.parse("{% include \"greeting\" %}!"); CHECK(env.render(t2, data) == "Hello Peter!"); - CHECK_THROWS_WITH(env.parse("{% include \"does-not-exist\" %}!"), - "[inja.exception.file_error] failed accessing file at 'does-not-exist'"); + CHECK_THROWS_WITH(env.parse("{% include \"does-not-exist\" %}!"), "[inja.exception.file_error] failed accessing file at 'does-not-exist'"); + + CHECK_THROWS_WITH(env.parse("{% include does-not-exist %}!"), "[inja.exception.parser_error] (at 1:12) expected string, got 'does-not-exist'"); + } + + SUBCASE("include-callback") { + inja::Environment env; + + CHECK_THROWS_WITH(env.parse("{% include \"does-not-exist\" %}!"), "[inja.exception.file_error] failed accessing file at 'does-not-exist'"); + + env.set_search_included_templates_in_files(false); + env.set_include_callback([&env](const std::string&, const std::string&) { return env.parse("Hello {{ name }}"); }); + + inja::Template t1 = env.parse("{% include \"greeting\" %}!"); + CHECK(env.render(t1, data) == "Hello Peter!"); + + env.set_search_included_templates_in_files(true); + env.set_include_callback([&env](const std::string&, const std::string& name) { return env.parse("Bye " + name); }); + + inja::Template t2 = env.parse("{% include \"Jeff\" %}!"); + CHECK(env.render(t2, data) == "Bye Jeff!"); } SUBCASE("include-in-loop") { - json loop_data; - loop_data["cities"] = json::array({{{"name", "Munich"}}, {{"name", "New York"}}}); + inja::json loop_data; + loop_data["cities"] = inja::json::array({{{"name", "Munich"}}, {{"name", "New York"}}}); inja::Environment env; env.include_template("city.tpl", env.parse("{{ loop.index }}:{{ city.name }};")); - CHECK(env.render("{% for city in cities %}{% include \"city.tpl\" %}{% endfor %}", loop_data) == - "0:Munich;1:New York;"); + CHECK(env.render("{% for city in cities %}{% include \"city.tpl\" %}{% endfor %}", loop_data) == "0:Munich;1:New York;"); } SUBCASE("count variables") { @@ -204,32 +225,38 @@ TEST_CASE("templates") { CHECK(env.render("Test\n {%- if is_happy %}{{ name }}{% endif %} ", data) == "Test\nPeter "); CHECK(env.render(" {%+ if is_happy %}{{ name }}{% endif %}", data) == " Peter"); CHECK(env.render(" {%- if is_happy %}{{ name }}{% endif -%} \n ", data) == "Peter"); - + CHECK(env.render(" {{- name -}} \n ", data) == "Peter"); CHECK(env.render("Test\n {{- name }} ", data) == "Test\nPeter "); CHECK(env.render(" {{ name }}\n ", data) == " Peter\n "); + CHECK(env.render("{{ name }}{# name -#} !", data) == "Peter!"); + CHECK(env.render(" {#- name -#} !", data) == "!"); // Nothing will be stripped if there are other characters before the start of the block. CHECK(env.render(". {%- if is_happy %}{{ name }}{% endif -%}\n", data) == ". Peter"); + CHECK(env.render(". {#- comment -#}\n.", data) == ". ."); env.set_lstrip_blocks(true); + CHECK(env.render("Hello {{ name }}!", data) == "Hello Peter!"); CHECK(env.render(" {% if is_happy %}{{ name }}{% endif %}", data) == "Peter"); CHECK(env.render(" {% if is_happy %}{{ name }}{% endif %} ", data) == "Peter "); CHECK(env.render(" {% if is_happy %}{{ name }}{% endif -%} ", data) == "Peter"); CHECK(env.render(" {%+ if is_happy %}{{ name }}{% endif %}", data) == " Peter"); CHECK(env.render("\n {%+ if is_happy %}{{ name }}{% endif -%} ", data) == "\n Peter"); CHECK(env.render("{% if is_happy %}{{ name }}{% endif %}\n", data) == "Peter\n"); + CHECK(env.render(" {# comment #}", data) == ""); env.set_trim_blocks(true); CHECK(env.render("{% if is_happy %}{{ name }}{% endif %}", data) == "Peter"); CHECK(env.render("{% if is_happy %}{{ name }}{% endif %}\n", data) == "Peter"); CHECK(env.render("{% if is_happy %}{{ name }}{% endif %} \n.", data) == "Peter."); CHECK(env.render("{%- if is_happy %}{{ name }}{% endif -%} \n.", data) == "Peter."); + CHECK(env.render(" {# comment #} \n.", data) == "."); } } TEST_CASE("other syntax") { - json data; + inja::json data; data["name"] = "Peter"; data["city"] = "Brunswick"; data["age"] = 29; diff --git a/test/test-units.cpp b/test/test-units.cpp index 95862f2..bbef190 100644 --- a/test/test-units.cpp +++ b/test/test-units.cpp @@ -27,7 +27,7 @@ Try this TEST_CASE("copy environment") { inja::Environment env; - env.add_callback("double", 1, [](inja::Arguments &args) { + env.add_callback("double", 1, [](inja::Arguments& args) { int number = args.at(0)->get(); return 2 * number; }); @@ -36,16 +36,16 @@ TEST_CASE("copy environment") { env.include_template("tpl", t1); std::string test_tpl = "{% include \"tpl\" %}"; - REQUIRE(env.render(test_tpl, json()) == "4"); + REQUIRE(env.render(test_tpl, inja::json()) == "4"); inja::Environment copy(env); - CHECK(copy.render(test_tpl, json()) == "4"); + CHECK(copy.render(test_tpl, inja::json()) == "4"); // overwrite template in source env inja::Template t2 = env.parse("{{ double(4) }}"); env.include_template("tpl", t2); - REQUIRE(env.render(test_tpl, json()) == "8"); + REQUIRE(env.render(test_tpl, inja::json()) == "8"); // template is unchanged in copy - CHECK(copy.render(test_tpl, json()) == "4"); + CHECK(copy.render(test_tpl, inja::json()) == "4"); } diff --git a/test/test.cpp b/test/test.cpp index 50161be..7645ade 100644 --- a/test/test.cpp +++ b/test/test.cpp @@ -5,8 +5,6 @@ #include "doctest/doctest.h" #include "inja/inja.hpp" -using json = nlohmann::json; - const std::string test_file_directory {"../test/data/"}; #include "test-files.cpp" diff --git a/third_party/include/doctest/LICENSE.txt b/third_party/include/doctest/LICENSE.txt index 6117758..c027373 100644 --- a/third_party/include/doctest/LICENSE.txt +++ b/third_party/include/doctest/LICENSE.txt @@ -1,6 +1,6 @@ The MIT License (MIT) -Copyright (c) 2016-2019 Viktor Kirilov +Copyright (c) 2016-2021 Viktor Kirilov Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/third_party/include/doctest/doctest.h b/third_party/include/doctest/doctest.h index c6bcafe..880e5bf 100644 --- a/third_party/include/doctest/doctest.h +++ b/third_party/include/doctest/doctest.h @@ -4,7 +4,7 @@ // // doctest.h - the lightest feature-rich C++ single-header testing framework for unit tests and TDD // -// Copyright (c) 2016-2019 Viktor Kirilov +// Copyright (c) 2016-2021 Viktor Kirilov // // Distributed under the MIT Software License // See accompanying file LICENSE.txt or copy at @@ -47,9 +47,9 @@ // ================================================================================================= #define DOCTEST_VERSION_MAJOR 2 -#define DOCTEST_VERSION_MINOR 3 -#define DOCTEST_VERSION_PATCH 7 -#define DOCTEST_VERSION_STR "2.3.7" +#define DOCTEST_VERSION_MINOR 4 +#define DOCTEST_VERSION_PATCH 6 +#define DOCTEST_VERSION_STR "2.4.6" #define DOCTEST_VERSION \ (DOCTEST_VERSION_MAJOR * 10000 + DOCTEST_VERSION_MINOR * 100 + DOCTEST_VERSION_PATCH) @@ -301,11 +301,23 @@ DOCTEST_MSVC_SUPPRESS_WARNING(26812) // Prefer 'enum class' over 'enum' #define DOCTEST_NOINLINE __declspec(noinline) #define DOCTEST_UNUSED #define DOCTEST_ALIGNMENT(x) -#else // MSVC +#elif DOCTEST_CLANG && DOCTEST_CLANG < DOCTEST_COMPILER(3, 5, 0) +#define DOCTEST_NOINLINE +#define DOCTEST_UNUSED +#define DOCTEST_ALIGNMENT(x) +#else #define DOCTEST_NOINLINE __attribute__((noinline)) #define DOCTEST_UNUSED __attribute__((unused)) #define DOCTEST_ALIGNMENT(x) __attribute__((aligned(x))) -#endif // MSVC +#endif + +#ifndef DOCTEST_NORETURN +#define DOCTEST_NORETURN [[noreturn]] +#endif // DOCTEST_NORETURN + +#ifndef DOCTEST_NOEXCEPT +#define DOCTEST_NOEXCEPT noexcept +#endif // DOCTEST_NOEXCEPT // ================================================================================================= // == FEATURE DETECTION END ======================================================================== @@ -342,13 +354,25 @@ DOCTEST_MSVC_SUPPRESS_WARNING(26812) // Prefer 'enum class' over 'enum' #define DOCTEST_GLOBAL_NO_WARNINGS(var) \ DOCTEST_CLANG_SUPPRESS_WARNING_WITH_PUSH("-Wglobal-constructors") \ DOCTEST_CLANG_SUPPRESS_WARNING("-Wunused-variable") \ - static int var DOCTEST_UNUSED // NOLINT(fuchsia-statically-constructed-objects,cert-err58-cpp) + static const int var DOCTEST_UNUSED // NOLINT(fuchsia-statically-constructed-objects,cert-err58-cpp) #define DOCTEST_GLOBAL_NO_WARNINGS_END() DOCTEST_CLANG_SUPPRESS_WARNING_POP #ifndef DOCTEST_BREAK_INTO_DEBUGGER // should probably take a look at https://github.com/scottt/debugbreak -#ifdef DOCTEST_PLATFORM_MAC -#define DOCTEST_BREAK_INTO_DEBUGGER() __asm__("int $3\n" : :) +#ifdef DOCTEST_PLATFORM_LINUX +#if defined(__GNUC__) && (defined(__i386) || defined(__x86_64)) +// Break at the location of the failing check if possible +#define DOCTEST_BREAK_INTO_DEBUGGER() __asm__("int $3\n" : :) // NOLINT (hicpp-no-assembler) +#else +#include +#define DOCTEST_BREAK_INTO_DEBUGGER() raise(SIGTRAP) +#endif +#elif defined(DOCTEST_PLATFORM_MAC) +#if defined(__x86_64) || defined(__x86_64__) || defined(__amd64__) || defined(__i386) +#define DOCTEST_BREAK_INTO_DEBUGGER() __asm__("int $3\n" : :) // NOLINT (hicpp-no-assembler) +#else +#define DOCTEST_BREAK_INTO_DEBUGGER() __asm__("brk #0"); // NOLINT (hicpp-no-assembler) +#endif #elif DOCTEST_MSVC #define DOCTEST_BREAK_INTO_DEBUGGER() __debugbreak() #elif defined(__MINGW32__) @@ -357,7 +381,7 @@ extern "C" __declspec(dllimport) void __stdcall DebugBreak(); DOCTEST_GCC_SUPPRESS_WARNING_POP #define DOCTEST_BREAK_INTO_DEBUGGER() ::DebugBreak() #else // linux -#define DOCTEST_BREAK_INTO_DEBUGGER() ((void)0) +#define DOCTEST_BREAK_INTO_DEBUGGER() (static_cast(0)) #endif // linux #endif // DOCTEST_BREAK_INTO_DEBUGGER @@ -367,6 +391,9 @@ DOCTEST_GCC_SUPPRESS_WARNING_POP #endif // DOCTEST_CONFIG_USE_IOSFWD #ifdef DOCTEST_CONFIG_USE_STD_HEADERS +#ifndef DOCTEST_CONFIG_INCLUDE_TYPE_TRAITS +#define DOCTEST_CONFIG_INCLUDE_TYPE_TRAITS +#endif // DOCTEST_CONFIG_INCLUDE_TYPE_TRAITS #include #include #include @@ -629,12 +656,14 @@ DOCTEST_INTERFACE const char* skipPathFromFilename(const char* file); struct DOCTEST_INTERFACE TestCaseData { - const char* m_file; // the file in which the test was registered + String m_file; // the file in which the test was registered (using String - see #350) unsigned m_line; // the line where the test was registered const char* m_name; // name of the test case const char* m_test_suite; // the test suite in which the test was added const char* m_description; bool m_skip; + bool m_no_breaks; + bool m_no_output; bool m_may_fail; bool m_should_fail; int m_expected_failures; @@ -688,12 +717,18 @@ struct DOCTEST_INTERFACE IContextScope virtual void stringify(std::ostream*) const = 0; }; +namespace detail { + struct DOCTEST_INTERFACE TestCase; +} // namespace detail + struct ContextOptions //!OCLINT too many fields { std::ostream* cout; // stdout stream - std::cout by default std::ostream* cerr; // stderr stream - std::cerr by default String binary_name; // the test binary name + const detail::TestCase* currentTest = nullptr; + // == parameters from the command line String out; // output filename String order_by; // how tests should be ordered @@ -720,7 +755,9 @@ struct ContextOptions //!OCLINT too many fields bool gnu_file_line; // if line numbers should be surrounded with :x: and not (x): bool no_path_in_filenames; // if the path to files should be removed from the output bool no_line_numbers; // if source code line numbers should be omitted from the output + bool no_debug_output; // no output in the debug console when a debugger is attached bool no_skipped_summary; // don't print "skipped" in the summary !!! UNDOCUMENTED !!! + bool no_time_in_output; // omit any time/timestamps from output !!! UNDOCUMENTED !!! bool help; // to print the help bool version; // to print the version @@ -731,7 +768,6 @@ struct ContextOptions //!OCLINT too many fields }; namespace detail { -#if defined(DOCTEST_CONFIG_TREAT_CHAR_STAR_AS_STRING) || defined(DOCTEST_CONFIG_INCLUDE_TYPE_TRAITS) template struct enable_if {}; @@ -739,15 +775,45 @@ namespace detail { template struct enable_if { typedef TYPE type; }; -#endif // DOCTEST_CONFIG_TREAT_CHAR_STAR_AS_STRING) || DOCTEST_CONFIG_INCLUDE_TYPE_TRAITS // clang-format off template struct remove_reference { typedef T type; }; template struct remove_reference { typedef T type; }; template struct remove_reference { typedef T type; }; + template U declval(int); + + template T declval(long); + + template auto declval() DOCTEST_NOEXCEPT -> decltype(declval(0)) ; + + template struct is_lvalue_reference { const static bool value=false; }; + template struct is_lvalue_reference { const static bool value=true; }; + + template + inline T&& forward(typename remove_reference::type& t) DOCTEST_NOEXCEPT + { + return static_cast(t); + } + + template + inline T&& forward(typename remove_reference::type&& t) DOCTEST_NOEXCEPT + { + static_assert(!is_lvalue_reference::value, + "Can not forward an rvalue as an lvalue."); + return static_cast(t); + } + template struct remove_const { typedef T type; }; template struct remove_const { typedef T type; }; +#ifdef DOCTEST_CONFIG_INCLUDE_TYPE_TRAITS + template struct is_enum : public std::is_enum {}; + template struct underlying_type : public std::underlying_type {}; +#else + // Use compiler intrinsics + template struct is_enum { constexpr static bool value = __is_enum(T); }; + template struct underlying_type { typedef __underlying_type(T) type; }; +#endif // clang-format on template @@ -756,33 +822,23 @@ namespace detail { { static const bool value = false; }; namespace has_insertion_operator_impl { - typedef char no; - typedef char yes[2]; + std::ostream &os(); + template + DOCTEST_REF_WRAP(T) val(); - struct any_t - { - template - // cppcheck-suppress noExplicitConstructor - any_t(const DOCTEST_REF_WRAP(T)); + template + struct check { + static constexpr bool value = false; }; - yes& testStreamable(std::ostream&); - no testStreamable(no); - - no operator<<(const std::ostream&, const any_t&); - - template - struct has_insertion_operator - { - static std::ostream& s; - static const DOCTEST_REF_WRAP(T) t; - static const bool value = sizeof(decltype(testStreamable(s << t))) == sizeof(yes); + template + struct check(), void())> { + static constexpr bool value = true; }; } // namespace has_insertion_operator_impl - template - struct has_insertion_operator : has_insertion_operator_impl::has_insertion_operator - {}; + template + using has_insertion_operator = has_insertion_operator_impl::check; DOCTEST_INTERFACE void my_memcpy(void* dest, const void* src, unsigned num); @@ -846,7 +902,7 @@ struct StringMaker } }; -template +template ::value, bool>::type = true> String toString(const DOCTEST_REF_WRAP(T) value) { return StringMaker::convert(value); } @@ -873,6 +929,12 @@ DOCTEST_INTERFACE String toString(int long long in); DOCTEST_INTERFACE String toString(int long long unsigned in); DOCTEST_INTERFACE String toString(std::nullptr_t in); +template ::value, bool>::type = true> +String toString(const DOCTEST_REF_WRAP(T) value) { + typedef typename detail::underlying_type::type UT; + return toString(static_cast(value)); +} + #if DOCTEST_MSVC >= DOCTEST_COMPILER(19, 20, 0) // see this issue on why this is needed: https://github.com/onqtam/doctest/issues/183 DOCTEST_INTERFACE String toString(const std::string& in); @@ -987,7 +1049,7 @@ namespace detail { DOCTEST_INTERFACE bool checkIfShouldThrow(assertType::Enum at); #ifndef DOCTEST_CONFIG_NO_EXCEPTIONS - [[noreturn]] + DOCTEST_NORETURN #endif // DOCTEST_CONFIG_NO_EXCEPTIONS DOCTEST_INTERFACE void throwException(); @@ -1005,13 +1067,24 @@ namespace detail { template String stringifyBinaryExpr(const DOCTEST_REF_WRAP(L) lhs, const char* op, const DOCTEST_REF_WRAP(R) rhs) { + // NOLINTNEXTLINE(clang-analyzer-cplusplus.NewDeleteLeaks) return toString(lhs) + op + toString(rhs); } +#if DOCTEST_CLANG && DOCTEST_CLANG < DOCTEST_COMPILER(3, 6, 0) +DOCTEST_CLANG_SUPPRESS_WARNING_WITH_PUSH("-Wunused-comparison") +#endif + +// This will check if there is any way it could find a operator like member or friend and uses it. +// If not it doesn't find the operator or if the operator at global scope is defined after +// this template, the template won't be instantiated due to SFINAE. Once the template is not +// instantiated it can look for global operator using normal conversions. +#define SFINAE_OP(ret,op) decltype(doctest::detail::declval() op doctest::detail::declval(),static_cast(0)) + #define DOCTEST_DO_BINARY_EXPRESSION_COMPARISON(op, op_str, op_macro) \ template \ - DOCTEST_NOINLINE Result operator op(const DOCTEST_REF_WRAP(R) rhs) { \ - bool res = op_macro(lhs, rhs); \ + DOCTEST_NOINLINE SFINAE_OP(Result,op) operator op(R&& rhs) { \ + bool res = op_macro(doctest::detail::forward(lhs), doctest::detail::forward(rhs)); \ if(m_at & assertType::is_false) \ res = !res; \ if(!res || doctest::getContextOptions()->success) \ @@ -1092,6 +1165,7 @@ namespace detail { #define DOCTEST_COMPARISON_RETURN_TYPE bool #else // DOCTEST_CONFIG_TREAT_CHAR_STAR_AS_STRING #define DOCTEST_COMPARISON_RETURN_TYPE typename enable_if::value || can_use_op::value, bool>::type + // NOLINTNEXTLINE(clang-analyzer-cplusplus.NewDeleteLeaks) inline bool eq(const char* lhs, const char* rhs) { return String(lhs) == String(rhs); } inline bool ne(const char* lhs, const char* rhs) { return String(lhs) != String(rhs); } inline bool lt(const char* lhs, const char* rhs) { return String(lhs) < String(rhs); } @@ -1138,12 +1212,16 @@ namespace detail { L lhs; assertType::Enum m_at; - explicit Expression_lhs(L in, assertType::Enum at) - : lhs(in) + explicit Expression_lhs(L&& in, assertType::Enum at) + : lhs(doctest::detail::forward(in)) , m_at(at) {} DOCTEST_NOINLINE operator Result() { - bool res = !!lhs; +// this is needed only foc MSVC 2015: +// https://ci.appveyor.com/project/onqtam/doctest/builds/38181202 +DOCTEST_MSVC_SUPPRESS_WARNING_WITH_PUSH(4800) // 'int': forcing value to bool + bool res = static_cast(lhs); +DOCTEST_MSVC_SUPPRESS_WARNING_POP if(m_at & assertType::is_false) //!OCLINT bitwise operator in conditional res = !res; @@ -1152,6 +1230,10 @@ namespace detail { return Result(res); } + /* This is required for user-defined conversions from Expression_lhs to L */ + //operator L() const { return lhs; } + operator L() const { return lhs; } + // clang-format off DOCTEST_DO_BINARY_EXPRESSION_COMPARISON(==, " == ", DOCTEST_CMP_EQ) //!OCLINT bitwise operator in conditional DOCTEST_DO_BINARY_EXPRESSION_COMPARISON(!=, " != ", DOCTEST_CMP_NE) //!OCLINT bitwise operator in conditional @@ -1192,6 +1274,10 @@ namespace detail { #endif // DOCTEST_CONFIG_NO_COMPARISON_WARNING_SUPPRESSION +#if DOCTEST_CLANG && DOCTEST_CLANG < DOCTEST_COMPILER(3, 6, 0) +DOCTEST_CLANG_SUPPRESS_WARNING_POP +#endif + struct DOCTEST_INTERFACE ExpressionDecomposer { assertType::Enum m_at; @@ -1203,8 +1289,8 @@ namespace detail { // https://github.com/catchorg/Catch2/issues/870 // https://github.com/catchorg/Catch2/issues/565 template - Expression_lhs operator<<(const DOCTEST_REF_WRAP(L) operand) { - return Expression_lhs(operand, m_at); + Expression_lhs operator<<(L &&operand) { + return Expression_lhs(doctest::detail::forward(operand), m_at); } }; @@ -1213,6 +1299,8 @@ namespace detail { const char* m_test_suite; const char* m_description; bool m_skip; + bool m_no_breaks; + bool m_no_output; bool m_may_fail; bool m_should_fail; int m_expected_failures; @@ -1284,12 +1372,12 @@ namespace detail { template struct RelationalComparator { bool operator()(const DOCTEST_REF_WRAP(L) lhs, const DOCTEST_REF_WRAP(R) rhs) const { return op(lhs, rhs); } }; // clang-format on - DOCTEST_BINARY_RELATIONAL_OP(0, eq) - DOCTEST_BINARY_RELATIONAL_OP(1, ne) - DOCTEST_BINARY_RELATIONAL_OP(2, gt) - DOCTEST_BINARY_RELATIONAL_OP(3, lt) - DOCTEST_BINARY_RELATIONAL_OP(4, ge) - DOCTEST_BINARY_RELATIONAL_OP(5, le) + DOCTEST_BINARY_RELATIONAL_OP(0, doctest::detail::eq) + DOCTEST_BINARY_RELATIONAL_OP(1, doctest::detail::ne) + DOCTEST_BINARY_RELATIONAL_OP(2, doctest::detail::gt) + DOCTEST_BINARY_RELATIONAL_OP(3, doctest::detail::lt) + DOCTEST_BINARY_RELATIONAL_OP(4, doctest::detail::ge) + DOCTEST_BINARY_RELATIONAL_OP(5, doctest::detail::le) struct DOCTEST_INTERFACE ResultBuilder : public AssertData { @@ -1416,9 +1504,9 @@ namespace detail { } catch(T ex) { // NOLINT res = m_translateFunction(ex); //!OCLINT parameter reassignment return true; - } catch(...) {} //!OCLINT - empty catch statement -#endif // DOCTEST_CONFIG_NO_EXCEPTIONS - ((void)res); // to silence -Wunused-parameter + } catch(...) {} //!OCLINT - empty catch statement +#endif // DOCTEST_CONFIG_NO_EXCEPTIONS + static_cast(res); // to silence -Wunused-parameter return false; } @@ -1491,7 +1579,7 @@ namespace detail { template class ContextScope : public ContextScopeBase { - const L &lambda_; + const L lambda_; public: explicit ContextScope(const L &lambda) : lambda_(lambda) {} @@ -1511,12 +1599,24 @@ namespace detail { MessageBuilder() = delete; ~MessageBuilder(); + // the preferred way of chaining parameters for stringification template - MessageBuilder& operator<<(const T& in) { + MessageBuilder& operator,(const T& in) { toStream(m_stream, in); return *this; } + // kept here just for backwards-compatibility - the comma operator should be preferred now + template + MessageBuilder& operator<<(const T& in) { return this->operator,(in); } + + // the `,` operator has the lowest operator precedence - if `<<` is used by the user then + // the `,` operator will be called last which is not what we want and thus the `*` operator + // is used first (has higher operator precedence compared to `<<`) so that we guarantee that + // an operator of the MessageBuilder class is called first before the rest of the parameters + template + MessageBuilder& operator*(const T& in) { return this->operator,(in); } + bool log(); void react(); }; @@ -1540,6 +1640,8 @@ namespace detail { DOCTEST_DEFINE_DECORATOR(test_suite, const char*, ""); DOCTEST_DEFINE_DECORATOR(description, const char*, ""); DOCTEST_DEFINE_DECORATOR(skip, bool, true); +DOCTEST_DEFINE_DECORATOR(no_breaks, bool, true); +DOCTEST_DEFINE_DECORATOR(no_output, bool, true); DOCTEST_DEFINE_DECORATOR(timeout, double, 0); DOCTEST_DEFINE_DECORATOR(may_fail, bool, true); DOCTEST_DEFINE_DECORATOR(should_fail, bool, true); @@ -1737,12 +1839,12 @@ int registerReporter(const char* name, int priority, bool isReporter) { #endif // DOCTEST_CONFIG_NO_TRY_CATCH_IN_ASSERTS #ifdef DOCTEST_CONFIG_VOID_CAST_EXPRESSIONS -#define DOCTEST_CAST_TO_VOID(x) \ +#define DOCTEST_CAST_TO_VOID(...) \ DOCTEST_GCC_SUPPRESS_WARNING_WITH_PUSH("-Wuseless-cast") \ - static_cast(x); \ + static_cast(__VA_ARGS__); \ DOCTEST_GCC_SUPPRESS_WARNING_POP #else // DOCTEST_CONFIG_VOID_CAST_EXPRESSIONS -#define DOCTEST_CAST_TO_VOID(x) x; +#define DOCTEST_CAST_TO_VOID(...) __VA_ARGS__; #endif // DOCTEST_CONFIG_VOID_CAST_EXPRESSIONS // registers the test by initializing a dummy var with a function @@ -1876,10 +1978,12 @@ int registerReporter(const char* name, int priority, bool isReporter) { static DOCTEST_NOINLINE doctest::detail::TestSuite& getCurrentTestSuite() { \ DOCTEST_MSVC_SUPPRESS_WARNING_WITH_PUSH(4640) \ DOCTEST_CLANG_SUPPRESS_WARNING_WITH_PUSH("-Wexit-time-destructors") \ - static doctest::detail::TestSuite data; \ + DOCTEST_GCC_SUPPRESS_WARNING_WITH_PUSH("-Wmissing-field-initializers") \ + static doctest::detail::TestSuite data{}; \ static bool inited = false; \ DOCTEST_MSVC_SUPPRESS_WARNING_POP \ DOCTEST_CLANG_SUPPRESS_WARNING_POP \ + DOCTEST_GCC_SUPPRESS_WARNING_POP \ if(!inited) { \ data* decorators; \ inited = true; \ @@ -1932,38 +2036,36 @@ int registerReporter(const char* name, int priority, bool isReporter) { DOCTEST_GLOBAL_NO_WARNINGS_END() typedef int DOCTEST_ANONYMOUS(_DOCTEST_ANON_FOR_SEMICOLON_) // for logging -#define DOCTEST_INFO(expression) \ +#define DOCTEST_INFO(...) \ DOCTEST_INFO_IMPL(DOCTEST_ANONYMOUS(_DOCTEST_CAPTURE_), DOCTEST_ANONYMOUS(_DOCTEST_CAPTURE_), \ - DOCTEST_ANONYMOUS(_DOCTEST_CAPTURE_), expression) + __VA_ARGS__) -#define DOCTEST_INFO_IMPL(lambda_name, mb_name, s_name, expression) \ - DOCTEST_MSVC_SUPPRESS_WARNING_WITH_PUSH(4626) \ - auto lambda_name = [&](std::ostream* s_name) { \ +#define DOCTEST_INFO_IMPL(mb_name, s_name, ...) \ + auto DOCTEST_ANONYMOUS(_DOCTEST_CAPTURE_) = doctest::detail::MakeContextScope( \ + [&](std::ostream* s_name) { \ doctest::detail::MessageBuilder mb_name(__FILE__, __LINE__, doctest::assertType::is_warn); \ mb_name.m_stream = s_name; \ - mb_name << expression; \ - }; \ - DOCTEST_MSVC_SUPPRESS_WARNING_POP \ - auto DOCTEST_ANONYMOUS(_DOCTEST_CAPTURE_) = doctest::detail::MakeContextScope(lambda_name) + mb_name * __VA_ARGS__; \ + }) -#define DOCTEST_CAPTURE(x) DOCTEST_INFO(#x " := " << x) +#define DOCTEST_CAPTURE(x) DOCTEST_INFO(#x " := ", x) -#define DOCTEST_ADD_AT_IMPL(type, file, line, mb, x) \ +#define DOCTEST_ADD_AT_IMPL(type, file, line, mb, ...) \ do { \ doctest::detail::MessageBuilder mb(file, line, doctest::assertType::type); \ - mb << x; \ + mb * __VA_ARGS__; \ DOCTEST_ASSERT_LOG_AND_REACT(mb); \ - } while((void)0, 0) + } while(false) // clang-format off -#define DOCTEST_ADD_MESSAGE_AT(file, line, x) DOCTEST_ADD_AT_IMPL(is_warn, file, line, DOCTEST_ANONYMOUS(_DOCTEST_MESSAGE_), x) -#define DOCTEST_ADD_FAIL_CHECK_AT(file, line, x) DOCTEST_ADD_AT_IMPL(is_check, file, line, DOCTEST_ANONYMOUS(_DOCTEST_MESSAGE_), x) -#define DOCTEST_ADD_FAIL_AT(file, line, x) DOCTEST_ADD_AT_IMPL(is_require, file, line, DOCTEST_ANONYMOUS(_DOCTEST_MESSAGE_), x) +#define DOCTEST_ADD_MESSAGE_AT(file, line, ...) DOCTEST_ADD_AT_IMPL(is_warn, file, line, DOCTEST_ANONYMOUS(_DOCTEST_MESSAGE_), __VA_ARGS__) +#define DOCTEST_ADD_FAIL_CHECK_AT(file, line, ...) DOCTEST_ADD_AT_IMPL(is_check, file, line, DOCTEST_ANONYMOUS(_DOCTEST_MESSAGE_), __VA_ARGS__) +#define DOCTEST_ADD_FAIL_AT(file, line, ...) DOCTEST_ADD_AT_IMPL(is_require, file, line, DOCTEST_ANONYMOUS(_DOCTEST_MESSAGE_), __VA_ARGS__) // clang-format on -#define DOCTEST_MESSAGE(x) DOCTEST_ADD_MESSAGE_AT(__FILE__, __LINE__, x) -#define DOCTEST_FAIL_CHECK(x) DOCTEST_ADD_FAIL_CHECK_AT(__FILE__, __LINE__, x) -#define DOCTEST_FAIL(x) DOCTEST_ADD_FAIL_AT(__FILE__, __LINE__, x) +#define DOCTEST_MESSAGE(...) DOCTEST_ADD_MESSAGE_AT(__FILE__, __LINE__, __VA_ARGS__) +#define DOCTEST_FAIL_CHECK(...) DOCTEST_ADD_FAIL_CHECK_AT(__FILE__, __LINE__, __VA_ARGS__) +#define DOCTEST_FAIL(...) DOCTEST_ADD_FAIL_AT(__FILE__, __LINE__, __VA_ARGS__) #define DOCTEST_TO_LVALUE(...) __VA_ARGS__ // Not removed to keep backwards compatibility. @@ -1982,7 +2084,7 @@ int registerReporter(const char* name, int priority, bool isReporter) { #define DOCTEST_ASSERT_IMPLEMENT_1(assert_type, ...) \ do { \ DOCTEST_ASSERT_IMPLEMENT_2(assert_type, __VA_ARGS__); \ - } while((void)0, 0) + } while(false) #else // DOCTEST_CONFIG_SUPER_FAST_ASSERTS @@ -2006,12 +2108,12 @@ int registerReporter(const char* name, int priority, bool isReporter) { #define DOCTEST_REQUIRE_FALSE(...) DOCTEST_ASSERT_IMPLEMENT_1(DT_REQUIRE_FALSE, __VA_ARGS__) // clang-format off -#define DOCTEST_WARN_MESSAGE(cond, msg) do { DOCTEST_INFO(msg); DOCTEST_ASSERT_IMPLEMENT_2(DT_WARN, cond); } while((void)0, 0) -#define DOCTEST_CHECK_MESSAGE(cond, msg) do { DOCTEST_INFO(msg); DOCTEST_ASSERT_IMPLEMENT_2(DT_CHECK, cond); } while((void)0, 0) -#define DOCTEST_REQUIRE_MESSAGE(cond, msg) do { DOCTEST_INFO(msg); DOCTEST_ASSERT_IMPLEMENT_2(DT_REQUIRE, cond); } while((void)0, 0) -#define DOCTEST_WARN_FALSE_MESSAGE(cond, msg) do { DOCTEST_INFO(msg); DOCTEST_ASSERT_IMPLEMENT_2(DT_WARN_FALSE, cond); } while((void)0, 0) -#define DOCTEST_CHECK_FALSE_MESSAGE(cond, msg) do { DOCTEST_INFO(msg); DOCTEST_ASSERT_IMPLEMENT_2(DT_CHECK_FALSE, cond); } while((void)0, 0) -#define DOCTEST_REQUIRE_FALSE_MESSAGE(cond, msg) do { DOCTEST_INFO(msg); DOCTEST_ASSERT_IMPLEMENT_2(DT_REQUIRE_FALSE, cond); } while((void)0, 0) +#define DOCTEST_WARN_MESSAGE(cond, ...) do { DOCTEST_INFO(__VA_ARGS__); DOCTEST_ASSERT_IMPLEMENT_2(DT_WARN, cond); } while(false) +#define DOCTEST_CHECK_MESSAGE(cond, ...) do { DOCTEST_INFO(__VA_ARGS__); DOCTEST_ASSERT_IMPLEMENT_2(DT_CHECK, cond); } while(false) +#define DOCTEST_REQUIRE_MESSAGE(cond, ...) do { DOCTEST_INFO(__VA_ARGS__); DOCTEST_ASSERT_IMPLEMENT_2(DT_REQUIRE, cond); } while(false) +#define DOCTEST_WARN_FALSE_MESSAGE(cond, ...) do { DOCTEST_INFO(__VA_ARGS__); DOCTEST_ASSERT_IMPLEMENT_2(DT_WARN_FALSE, cond); } while(false) +#define DOCTEST_CHECK_FALSE_MESSAGE(cond, ...) do { DOCTEST_INFO(__VA_ARGS__); DOCTEST_ASSERT_IMPLEMENT_2(DT_CHECK_FALSE, cond); } while(false) +#define DOCTEST_REQUIRE_FALSE_MESSAGE(cond, ...) do { DOCTEST_INFO(__VA_ARGS__); DOCTEST_ASSERT_IMPLEMENT_2(DT_REQUIRE_FALSE, cond); } while(false) // clang-format on #define DOCTEST_ASSERT_THROWS_AS(expr, assert_type, message, ...) \ @@ -2021,73 +2123,73 @@ int registerReporter(const char* name, int priority, bool isReporter) { __LINE__, #expr, #__VA_ARGS__, message); \ try { \ DOCTEST_CAST_TO_VOID(expr) \ - } catch(const doctest::detail::remove_const< \ - doctest::detail::remove_reference<__VA_ARGS__>::type>::type&) { \ + } catch(const typename doctest::detail::remove_const< \ + typename doctest::detail::remove_reference<__VA_ARGS__>::type>::type&) { \ _DOCTEST_RB.translateException(); \ _DOCTEST_RB.m_threw_as = true; \ } catch(...) { _DOCTEST_RB.translateException(); } \ DOCTEST_ASSERT_LOG_AND_REACT(_DOCTEST_RB); \ } \ - } while((void)0, 0) + } while(false) -#define DOCTEST_ASSERT_THROWS_WITH(expr, assert_type, ...) \ +#define DOCTEST_ASSERT_THROWS_WITH(expr, expr_str, assert_type, ...) \ do { \ if(!doctest::getContextOptions()->no_throw) { \ doctest::detail::ResultBuilder _DOCTEST_RB(doctest::assertType::assert_type, __FILE__, \ - __LINE__, #expr, "", __VA_ARGS__); \ + __LINE__, expr_str, "", __VA_ARGS__); \ try { \ DOCTEST_CAST_TO_VOID(expr) \ } catch(...) { _DOCTEST_RB.translateException(); } \ DOCTEST_ASSERT_LOG_AND_REACT(_DOCTEST_RB); \ } \ - } while((void)0, 0) + } while(false) -#define DOCTEST_ASSERT_NOTHROW(expr, assert_type) \ +#define DOCTEST_ASSERT_NOTHROW(assert_type, ...) \ do { \ doctest::detail::ResultBuilder _DOCTEST_RB(doctest::assertType::assert_type, __FILE__, \ - __LINE__, #expr); \ + __LINE__, #__VA_ARGS__); \ try { \ - DOCTEST_CAST_TO_VOID(expr) \ + DOCTEST_CAST_TO_VOID(__VA_ARGS__) \ } catch(...) { _DOCTEST_RB.translateException(); } \ DOCTEST_ASSERT_LOG_AND_REACT(_DOCTEST_RB); \ - } while((void)0, 0) + } while(false) // clang-format off -#define DOCTEST_WARN_THROWS(expr) DOCTEST_ASSERT_THROWS_WITH(expr, DT_WARN_THROWS, "") -#define DOCTEST_CHECK_THROWS(expr) DOCTEST_ASSERT_THROWS_WITH(expr, DT_CHECK_THROWS, "") -#define DOCTEST_REQUIRE_THROWS(expr) DOCTEST_ASSERT_THROWS_WITH(expr, DT_REQUIRE_THROWS, "") +#define DOCTEST_WARN_THROWS(...) DOCTEST_ASSERT_THROWS_WITH((__VA_ARGS__), #__VA_ARGS__, DT_WARN_THROWS, "") +#define DOCTEST_CHECK_THROWS(...) DOCTEST_ASSERT_THROWS_WITH((__VA_ARGS__), #__VA_ARGS__, DT_CHECK_THROWS, "") +#define DOCTEST_REQUIRE_THROWS(...) DOCTEST_ASSERT_THROWS_WITH((__VA_ARGS__), #__VA_ARGS__, DT_REQUIRE_THROWS, "") #define DOCTEST_WARN_THROWS_AS(expr, ...) DOCTEST_ASSERT_THROWS_AS(expr, DT_WARN_THROWS_AS, "", __VA_ARGS__) #define DOCTEST_CHECK_THROWS_AS(expr, ...) DOCTEST_ASSERT_THROWS_AS(expr, DT_CHECK_THROWS_AS, "", __VA_ARGS__) #define DOCTEST_REQUIRE_THROWS_AS(expr, ...) DOCTEST_ASSERT_THROWS_AS(expr, DT_REQUIRE_THROWS_AS, "", __VA_ARGS__) -#define DOCTEST_WARN_THROWS_WITH(expr, ...) DOCTEST_ASSERT_THROWS_WITH(expr, DT_WARN_THROWS_WITH, __VA_ARGS__) -#define DOCTEST_CHECK_THROWS_WITH(expr, ...) DOCTEST_ASSERT_THROWS_WITH(expr, DT_CHECK_THROWS_WITH, __VA_ARGS__) -#define DOCTEST_REQUIRE_THROWS_WITH(expr, ...) DOCTEST_ASSERT_THROWS_WITH(expr, DT_REQUIRE_THROWS_WITH, __VA_ARGS__) +#define DOCTEST_WARN_THROWS_WITH(expr, ...) DOCTEST_ASSERT_THROWS_WITH(expr, #expr, DT_WARN_THROWS_WITH, __VA_ARGS__) +#define DOCTEST_CHECK_THROWS_WITH(expr, ...) DOCTEST_ASSERT_THROWS_WITH(expr, #expr, DT_CHECK_THROWS_WITH, __VA_ARGS__) +#define DOCTEST_REQUIRE_THROWS_WITH(expr, ...) DOCTEST_ASSERT_THROWS_WITH(expr, #expr, DT_REQUIRE_THROWS_WITH, __VA_ARGS__) #define DOCTEST_WARN_THROWS_WITH_AS(expr, message, ...) DOCTEST_ASSERT_THROWS_AS(expr, DT_WARN_THROWS_WITH_AS, message, __VA_ARGS__) #define DOCTEST_CHECK_THROWS_WITH_AS(expr, message, ...) DOCTEST_ASSERT_THROWS_AS(expr, DT_CHECK_THROWS_WITH_AS, message, __VA_ARGS__) #define DOCTEST_REQUIRE_THROWS_WITH_AS(expr, message, ...) DOCTEST_ASSERT_THROWS_AS(expr, DT_REQUIRE_THROWS_WITH_AS, message, __VA_ARGS__) -#define DOCTEST_WARN_NOTHROW(expr) DOCTEST_ASSERT_NOTHROW(expr, DT_WARN_NOTHROW) -#define DOCTEST_CHECK_NOTHROW(expr) DOCTEST_ASSERT_NOTHROW(expr, DT_CHECK_NOTHROW) -#define DOCTEST_REQUIRE_NOTHROW(expr) DOCTEST_ASSERT_NOTHROW(expr, DT_REQUIRE_NOTHROW) - -#define DOCTEST_WARN_THROWS_MESSAGE(expr, msg) do { DOCTEST_INFO(msg); DOCTEST_WARN_THROWS(expr); } while((void)0, 0) -#define DOCTEST_CHECK_THROWS_MESSAGE(expr, msg) do { DOCTEST_INFO(msg); DOCTEST_CHECK_THROWS(expr); } while((void)0, 0) -#define DOCTEST_REQUIRE_THROWS_MESSAGE(expr, msg) do { DOCTEST_INFO(msg); DOCTEST_REQUIRE_THROWS(expr); } while((void)0, 0) -#define DOCTEST_WARN_THROWS_AS_MESSAGE(expr, ex, msg) do { DOCTEST_INFO(msg); DOCTEST_WARN_THROWS_AS(expr, ex); } while((void)0, 0) -#define DOCTEST_CHECK_THROWS_AS_MESSAGE(expr, ex, msg) do { DOCTEST_INFO(msg); DOCTEST_CHECK_THROWS_AS(expr, ex); } while((void)0, 0) -#define DOCTEST_REQUIRE_THROWS_AS_MESSAGE(expr, ex, msg) do { DOCTEST_INFO(msg); DOCTEST_REQUIRE_THROWS_AS(expr, ex); } while((void)0, 0) -#define DOCTEST_WARN_THROWS_WITH_MESSAGE(expr, with, msg) do { DOCTEST_INFO(msg); DOCTEST_WARN_THROWS_WITH(expr, with); } while((void)0, 0) -#define DOCTEST_CHECK_THROWS_WITH_MESSAGE(expr, with, msg) do { DOCTEST_INFO(msg); DOCTEST_CHECK_THROWS_WITH(expr, with); } while((void)0, 0) -#define DOCTEST_REQUIRE_THROWS_WITH_MESSAGE(expr, with, msg) do { DOCTEST_INFO(msg); DOCTEST_REQUIRE_THROWS_WITH(expr, with); } while((void)0, 0) -#define DOCTEST_WARN_THROWS_WITH_AS_MESSAGE(expr, with, ex, msg) do { DOCTEST_INFO(msg); DOCTEST_WARN_THROWS_WITH_AS(expr, with, ex); } while((void)0, 0) -#define DOCTEST_CHECK_THROWS_WITH_AS_MESSAGE(expr, with, ex, msg) do { DOCTEST_INFO(msg); DOCTEST_CHECK_THROWS_WITH_AS(expr, with, ex); } while((void)0, 0) -#define DOCTEST_REQUIRE_THROWS_WITH_AS_MESSAGE(expr, with, ex, msg) do { DOCTEST_INFO(msg); DOCTEST_REQUIRE_THROWS_WITH_AS(expr, with, ex); } while((void)0, 0) -#define DOCTEST_WARN_NOTHROW_MESSAGE(expr, msg) do { DOCTEST_INFO(msg); DOCTEST_WARN_NOTHROW(expr); } while((void)0, 0) -#define DOCTEST_CHECK_NOTHROW_MESSAGE(expr, msg) do { DOCTEST_INFO(msg); DOCTEST_CHECK_NOTHROW(expr); } while((void)0, 0) -#define DOCTEST_REQUIRE_NOTHROW_MESSAGE(expr, msg) do { DOCTEST_INFO(msg); DOCTEST_REQUIRE_NOTHROW(expr); } while((void)0, 0) +#define DOCTEST_WARN_NOTHROW(...) DOCTEST_ASSERT_NOTHROW(DT_WARN_NOTHROW, __VA_ARGS__) +#define DOCTEST_CHECK_NOTHROW(...) DOCTEST_ASSERT_NOTHROW(DT_CHECK_NOTHROW, __VA_ARGS__) +#define DOCTEST_REQUIRE_NOTHROW(...) DOCTEST_ASSERT_NOTHROW(DT_REQUIRE_NOTHROW, __VA_ARGS__) + +#define DOCTEST_WARN_THROWS_MESSAGE(expr, ...) do { DOCTEST_INFO(__VA_ARGS__); DOCTEST_WARN_THROWS(expr); } while(false) +#define DOCTEST_CHECK_THROWS_MESSAGE(expr, ...) do { DOCTEST_INFO(__VA_ARGS__); DOCTEST_CHECK_THROWS(expr); } while(false) +#define DOCTEST_REQUIRE_THROWS_MESSAGE(expr, ...) do { DOCTEST_INFO(__VA_ARGS__); DOCTEST_REQUIRE_THROWS(expr); } while(false) +#define DOCTEST_WARN_THROWS_AS_MESSAGE(expr, ex, ...) do { DOCTEST_INFO(__VA_ARGS__); DOCTEST_WARN_THROWS_AS(expr, ex); } while(false) +#define DOCTEST_CHECK_THROWS_AS_MESSAGE(expr, ex, ...) do { DOCTEST_INFO(__VA_ARGS__); DOCTEST_CHECK_THROWS_AS(expr, ex); } while(false) +#define DOCTEST_REQUIRE_THROWS_AS_MESSAGE(expr, ex, ...) do { DOCTEST_INFO(__VA_ARGS__); DOCTEST_REQUIRE_THROWS_AS(expr, ex); } while(false) +#define DOCTEST_WARN_THROWS_WITH_MESSAGE(expr, with, ...) do { DOCTEST_INFO(__VA_ARGS__); DOCTEST_WARN_THROWS_WITH(expr, with); } while(false) +#define DOCTEST_CHECK_THROWS_WITH_MESSAGE(expr, with, ...) do { DOCTEST_INFO(__VA_ARGS__); DOCTEST_CHECK_THROWS_WITH(expr, with); } while(false) +#define DOCTEST_REQUIRE_THROWS_WITH_MESSAGE(expr, with, ...) do { DOCTEST_INFO(__VA_ARGS__); DOCTEST_REQUIRE_THROWS_WITH(expr, with); } while(false) +#define DOCTEST_WARN_THROWS_WITH_AS_MESSAGE(expr, with, ex, ...) do { DOCTEST_INFO(__VA_ARGS__); DOCTEST_WARN_THROWS_WITH_AS(expr, with, ex); } while(false) +#define DOCTEST_CHECK_THROWS_WITH_AS_MESSAGE(expr, with, ex, ...) do { DOCTEST_INFO(__VA_ARGS__); DOCTEST_CHECK_THROWS_WITH_AS(expr, with, ex); } while(false) +#define DOCTEST_REQUIRE_THROWS_WITH_AS_MESSAGE(expr, with, ex, ...) do { DOCTEST_INFO(__VA_ARGS__); DOCTEST_REQUIRE_THROWS_WITH_AS(expr, with, ex); } while(false) +#define DOCTEST_WARN_NOTHROW_MESSAGE(expr, ...) do { DOCTEST_INFO(__VA_ARGS__); DOCTEST_WARN_NOTHROW(expr); } while(false) +#define DOCTEST_CHECK_NOTHROW_MESSAGE(expr, ...) do { DOCTEST_INFO(__VA_ARGS__); DOCTEST_CHECK_NOTHROW(expr); } while(false) +#define DOCTEST_REQUIRE_NOTHROW_MESSAGE(expr, ...) do { DOCTEST_INFO(__VA_ARGS__); DOCTEST_REQUIRE_NOTHROW(expr); } while(false) // clang-format on #ifndef DOCTEST_CONFIG_SUPER_FAST_ASSERTS @@ -2100,7 +2202,7 @@ int registerReporter(const char* name, int priority, bool isReporter) { _DOCTEST_RB.binary_assert( \ __VA_ARGS__)) \ DOCTEST_ASSERT_LOG_AND_REACT(_DOCTEST_RB); \ - } while((void)0, 0) + } while(false) #define DOCTEST_UNARY_ASSERT(assert_type, ...) \ do { \ @@ -2108,7 +2210,7 @@ int registerReporter(const char* name, int priority, bool isReporter) { __LINE__, #__VA_ARGS__); \ DOCTEST_WRAP_IN_TRY(_DOCTEST_RB.unary_assert(__VA_ARGS__)) \ DOCTEST_ASSERT_LOG_AND_REACT(_DOCTEST_RB); \ - } while((void)0, 0) + } while(false) #else // DOCTEST_CONFIG_SUPER_FAST_ASSERTS @@ -2184,37 +2286,37 @@ int registerReporter(const char* name, int priority, bool isReporter) { #ifdef DOCTEST_CONFIG_NO_EXCEPTIONS_BUT_WITH_ALL_ASSERTS -#define DOCTEST_WARN_THROWS(expr) ((void)0) -#define DOCTEST_CHECK_THROWS(expr) ((void)0) -#define DOCTEST_REQUIRE_THROWS(expr) ((void)0) -#define DOCTEST_WARN_THROWS_AS(expr, ...) ((void)0) -#define DOCTEST_CHECK_THROWS_AS(expr, ...) ((void)0) -#define DOCTEST_REQUIRE_THROWS_AS(expr, ...) ((void)0) -#define DOCTEST_WARN_THROWS_WITH(expr, ...) ((void)0) -#define DOCTEST_CHECK_THROWS_WITH(expr, ...) ((void)0) -#define DOCTEST_REQUIRE_THROWS_WITH(expr, ...) ((void)0) -#define DOCTEST_WARN_THROWS_WITH_AS(expr, with, ...) ((void)0) -#define DOCTEST_CHECK_THROWS_WITH_AS(expr, with, ...) ((void)0) -#define DOCTEST_REQUIRE_THROWS_WITH_AS(expr, with, ...) ((void)0) -#define DOCTEST_WARN_NOTHROW(expr) ((void)0) -#define DOCTEST_CHECK_NOTHROW(expr) ((void)0) -#define DOCTEST_REQUIRE_NOTHROW(expr) ((void)0) - -#define DOCTEST_WARN_THROWS_MESSAGE(expr, msg) ((void)0) -#define DOCTEST_CHECK_THROWS_MESSAGE(expr, msg) ((void)0) -#define DOCTEST_REQUIRE_THROWS_MESSAGE(expr, msg) ((void)0) -#define DOCTEST_WARN_THROWS_AS_MESSAGE(expr, ex, msg) ((void)0) -#define DOCTEST_CHECK_THROWS_AS_MESSAGE(expr, ex, msg) ((void)0) -#define DOCTEST_REQUIRE_THROWS_AS_MESSAGE(expr, ex, msg) ((void)0) -#define DOCTEST_WARN_THROWS_WITH_MESSAGE(expr, with, msg) ((void)0) -#define DOCTEST_CHECK_THROWS_WITH_MESSAGE(expr, with, msg) ((void)0) -#define DOCTEST_REQUIRE_THROWS_WITH_MESSAGE(expr, with, msg) ((void)0) -#define DOCTEST_WARN_THROWS_WITH_AS_MESSAGE(expr, with, ex, msg) ((void)0) -#define DOCTEST_CHECK_THROWS_WITH_AS_MESSAGE(expr, with, ex, msg) ((void)0) -#define DOCTEST_REQUIRE_THROWS_WITH_AS_MESSAGE(expr, with, ex, msg) ((void)0) -#define DOCTEST_WARN_NOTHROW_MESSAGE(expr, msg) ((void)0) -#define DOCTEST_CHECK_NOTHROW_MESSAGE(expr, msg) ((void)0) -#define DOCTEST_REQUIRE_NOTHROW_MESSAGE(expr, msg) ((void)0) +#define DOCTEST_WARN_THROWS(...) (static_cast(0)) +#define DOCTEST_CHECK_THROWS(...) (static_cast(0)) +#define DOCTEST_REQUIRE_THROWS(...) (static_cast(0)) +#define DOCTEST_WARN_THROWS_AS(expr, ...) (static_cast(0)) +#define DOCTEST_CHECK_THROWS_AS(expr, ...) (static_cast(0)) +#define DOCTEST_REQUIRE_THROWS_AS(expr, ...) (static_cast(0)) +#define DOCTEST_WARN_THROWS_WITH(expr, ...) (static_cast(0)) +#define DOCTEST_CHECK_THROWS_WITH(expr, ...) (static_cast(0)) +#define DOCTEST_REQUIRE_THROWS_WITH(expr, ...) (static_cast(0)) +#define DOCTEST_WARN_THROWS_WITH_AS(expr, with, ...) (static_cast(0)) +#define DOCTEST_CHECK_THROWS_WITH_AS(expr, with, ...) (static_cast(0)) +#define DOCTEST_REQUIRE_THROWS_WITH_AS(expr, with, ...) (static_cast(0)) +#define DOCTEST_WARN_NOTHROW(...) (static_cast(0)) +#define DOCTEST_CHECK_NOTHROW(...) (static_cast(0)) +#define DOCTEST_REQUIRE_NOTHROW(...) (static_cast(0)) + +#define DOCTEST_WARN_THROWS_MESSAGE(expr, ...) (static_cast(0)) +#define DOCTEST_CHECK_THROWS_MESSAGE(expr, ...) (static_cast(0)) +#define DOCTEST_REQUIRE_THROWS_MESSAGE(expr, ...) (static_cast(0)) +#define DOCTEST_WARN_THROWS_AS_MESSAGE(expr, ex, ...) (static_cast(0)) +#define DOCTEST_CHECK_THROWS_AS_MESSAGE(expr, ex, ...) (static_cast(0)) +#define DOCTEST_REQUIRE_THROWS_AS_MESSAGE(expr, ex, ...) (static_cast(0)) +#define DOCTEST_WARN_THROWS_WITH_MESSAGE(expr, with, ...) (static_cast(0)) +#define DOCTEST_CHECK_THROWS_WITH_MESSAGE(expr, with, ...) (static_cast(0)) +#define DOCTEST_REQUIRE_THROWS_WITH_MESSAGE(expr, with, ...) (static_cast(0)) +#define DOCTEST_WARN_THROWS_WITH_AS_MESSAGE(expr, with, ex, ...) (static_cast(0)) +#define DOCTEST_CHECK_THROWS_WITH_AS_MESSAGE(expr, with, ex, ...) (static_cast(0)) +#define DOCTEST_REQUIRE_THROWS_WITH_AS_MESSAGE(expr, with, ex, ...) (static_cast(0)) +#define DOCTEST_WARN_NOTHROW_MESSAGE(expr, ...) (static_cast(0)) +#define DOCTEST_CHECK_NOTHROW_MESSAGE(expr, ...) (static_cast(0)) +#define DOCTEST_REQUIRE_NOTHROW_MESSAGE(expr, ...) (static_cast(0)) #else // DOCTEST_CONFIG_NO_EXCEPTIONS_BUT_WITH_ALL_ASSERTS @@ -2305,86 +2407,86 @@ int registerReporter(const char* name, int priority, bool isReporter) { #define DOCTEST_REGISTER_REPORTER(name, priority, reporter) #define DOCTEST_REGISTER_LISTENER(name, priority, reporter) -#define DOCTEST_INFO(x) ((void)0) -#define DOCTEST_CAPTURE(x) ((void)0) -#define DOCTEST_ADD_MESSAGE_AT(file, line, x) ((void)0) -#define DOCTEST_ADD_FAIL_CHECK_AT(file, line, x) ((void)0) -#define DOCTEST_ADD_FAIL_AT(file, line, x) ((void)0) -#define DOCTEST_MESSAGE(x) ((void)0) -#define DOCTEST_FAIL_CHECK(x) ((void)0) -#define DOCTEST_FAIL(x) ((void)0) - -#define DOCTEST_WARN(...) ((void)0) -#define DOCTEST_CHECK(...) ((void)0) -#define DOCTEST_REQUIRE(...) ((void)0) -#define DOCTEST_WARN_FALSE(...) ((void)0) -#define DOCTEST_CHECK_FALSE(...) ((void)0) -#define DOCTEST_REQUIRE_FALSE(...) ((void)0) - -#define DOCTEST_WARN_MESSAGE(cond, msg) ((void)0) -#define DOCTEST_CHECK_MESSAGE(cond, msg) ((void)0) -#define DOCTEST_REQUIRE_MESSAGE(cond, msg) ((void)0) -#define DOCTEST_WARN_FALSE_MESSAGE(cond, msg) ((void)0) -#define DOCTEST_CHECK_FALSE_MESSAGE(cond, msg) ((void)0) -#define DOCTEST_REQUIRE_FALSE_MESSAGE(cond, msg) ((void)0) - -#define DOCTEST_WARN_THROWS(expr) ((void)0) -#define DOCTEST_CHECK_THROWS(expr) ((void)0) -#define DOCTEST_REQUIRE_THROWS(expr) ((void)0) -#define DOCTEST_WARN_THROWS_AS(expr, ...) ((void)0) -#define DOCTEST_CHECK_THROWS_AS(expr, ...) ((void)0) -#define DOCTEST_REQUIRE_THROWS_AS(expr, ...) ((void)0) -#define DOCTEST_WARN_THROWS_WITH(expr, ...) ((void)0) -#define DOCTEST_CHECK_THROWS_WITH(expr, ...) ((void)0) -#define DOCTEST_REQUIRE_THROWS_WITH(expr, ...) ((void)0) -#define DOCTEST_WARN_THROWS_WITH_AS(expr, with, ...) ((void)0) -#define DOCTEST_CHECK_THROWS_WITH_AS(expr, with, ...) ((void)0) -#define DOCTEST_REQUIRE_THROWS_WITH_AS(expr, with, ...) ((void)0) -#define DOCTEST_WARN_NOTHROW(expr) ((void)0) -#define DOCTEST_CHECK_NOTHROW(expr) ((void)0) -#define DOCTEST_REQUIRE_NOTHROW(expr) ((void)0) - -#define DOCTEST_WARN_THROWS_MESSAGE(expr, msg) ((void)0) -#define DOCTEST_CHECK_THROWS_MESSAGE(expr, msg) ((void)0) -#define DOCTEST_REQUIRE_THROWS_MESSAGE(expr, msg) ((void)0) -#define DOCTEST_WARN_THROWS_AS_MESSAGE(expr, ex, msg) ((void)0) -#define DOCTEST_CHECK_THROWS_AS_MESSAGE(expr, ex, msg) ((void)0) -#define DOCTEST_REQUIRE_THROWS_AS_MESSAGE(expr, ex, msg) ((void)0) -#define DOCTEST_WARN_THROWS_WITH_MESSAGE(expr, with, msg) ((void)0) -#define DOCTEST_CHECK_THROWS_WITH_MESSAGE(expr, with, msg) ((void)0) -#define DOCTEST_REQUIRE_THROWS_WITH_MESSAGE(expr, with, msg) ((void)0) -#define DOCTEST_WARN_THROWS_WITH_AS_MESSAGE(expr, with, ex, msg) ((void)0) -#define DOCTEST_CHECK_THROWS_WITH_AS_MESSAGE(expr, with, ex, msg) ((void)0) -#define DOCTEST_REQUIRE_THROWS_WITH_AS_MESSAGE(expr, with, ex, msg) ((void)0) -#define DOCTEST_WARN_NOTHROW_MESSAGE(expr, msg) ((void)0) -#define DOCTEST_CHECK_NOTHROW_MESSAGE(expr, msg) ((void)0) -#define DOCTEST_REQUIRE_NOTHROW_MESSAGE(expr, msg) ((void)0) - -#define DOCTEST_WARN_EQ(...) ((void)0) -#define DOCTEST_CHECK_EQ(...) ((void)0) -#define DOCTEST_REQUIRE_EQ(...) ((void)0) -#define DOCTEST_WARN_NE(...) ((void)0) -#define DOCTEST_CHECK_NE(...) ((void)0) -#define DOCTEST_REQUIRE_NE(...) ((void)0) -#define DOCTEST_WARN_GT(...) ((void)0) -#define DOCTEST_CHECK_GT(...) ((void)0) -#define DOCTEST_REQUIRE_GT(...) ((void)0) -#define DOCTEST_WARN_LT(...) ((void)0) -#define DOCTEST_CHECK_LT(...) ((void)0) -#define DOCTEST_REQUIRE_LT(...) ((void)0) -#define DOCTEST_WARN_GE(...) ((void)0) -#define DOCTEST_CHECK_GE(...) ((void)0) -#define DOCTEST_REQUIRE_GE(...) ((void)0) -#define DOCTEST_WARN_LE(...) ((void)0) -#define DOCTEST_CHECK_LE(...) ((void)0) -#define DOCTEST_REQUIRE_LE(...) ((void)0) - -#define DOCTEST_WARN_UNARY(...) ((void)0) -#define DOCTEST_CHECK_UNARY(...) ((void)0) -#define DOCTEST_REQUIRE_UNARY(...) ((void)0) -#define DOCTEST_WARN_UNARY_FALSE(...) ((void)0) -#define DOCTEST_CHECK_UNARY_FALSE(...) ((void)0) -#define DOCTEST_REQUIRE_UNARY_FALSE(...) ((void)0) +#define DOCTEST_INFO(...) (static_cast(0)) +#define DOCTEST_CAPTURE(x) (static_cast(0)) +#define DOCTEST_ADD_MESSAGE_AT(file, line, ...) (static_cast(0)) +#define DOCTEST_ADD_FAIL_CHECK_AT(file, line, ...) (static_cast(0)) +#define DOCTEST_ADD_FAIL_AT(file, line, ...) (static_cast(0)) +#define DOCTEST_MESSAGE(...) (static_cast(0)) +#define DOCTEST_FAIL_CHECK(...) (static_cast(0)) +#define DOCTEST_FAIL(...) (static_cast(0)) + +#define DOCTEST_WARN(...) (static_cast(0)) +#define DOCTEST_CHECK(...) (static_cast(0)) +#define DOCTEST_REQUIRE(...) (static_cast(0)) +#define DOCTEST_WARN_FALSE(...) (static_cast(0)) +#define DOCTEST_CHECK_FALSE(...) (static_cast(0)) +#define DOCTEST_REQUIRE_FALSE(...) (static_cast(0)) + +#define DOCTEST_WARN_MESSAGE(cond, ...) (static_cast(0)) +#define DOCTEST_CHECK_MESSAGE(cond, ...) (static_cast(0)) +#define DOCTEST_REQUIRE_MESSAGE(cond, ...) (static_cast(0)) +#define DOCTEST_WARN_FALSE_MESSAGE(cond, ...) (static_cast(0)) +#define DOCTEST_CHECK_FALSE_MESSAGE(cond, ...) (static_cast(0)) +#define DOCTEST_REQUIRE_FALSE_MESSAGE(cond, ...) (static_cast(0)) + +#define DOCTEST_WARN_THROWS(...) (static_cast(0)) +#define DOCTEST_CHECK_THROWS(...) (static_cast(0)) +#define DOCTEST_REQUIRE_THROWS(...) (static_cast(0)) +#define DOCTEST_WARN_THROWS_AS(expr, ...) (static_cast(0)) +#define DOCTEST_CHECK_THROWS_AS(expr, ...) (static_cast(0)) +#define DOCTEST_REQUIRE_THROWS_AS(expr, ...) (static_cast(0)) +#define DOCTEST_WARN_THROWS_WITH(expr, ...) (static_cast(0)) +#define DOCTEST_CHECK_THROWS_WITH(expr, ...) (static_cast(0)) +#define DOCTEST_REQUIRE_THROWS_WITH(expr, ...) (static_cast(0)) +#define DOCTEST_WARN_THROWS_WITH_AS(expr, with, ...) (static_cast(0)) +#define DOCTEST_CHECK_THROWS_WITH_AS(expr, with, ...) (static_cast(0)) +#define DOCTEST_REQUIRE_THROWS_WITH_AS(expr, with, ...) (static_cast(0)) +#define DOCTEST_WARN_NOTHROW(...) (static_cast(0)) +#define DOCTEST_CHECK_NOTHROW(...) (static_cast(0)) +#define DOCTEST_REQUIRE_NOTHROW(...) (static_cast(0)) + +#define DOCTEST_WARN_THROWS_MESSAGE(expr, ...) (static_cast(0)) +#define DOCTEST_CHECK_THROWS_MESSAGE(expr, ...) (static_cast(0)) +#define DOCTEST_REQUIRE_THROWS_MESSAGE(expr, ...) (static_cast(0)) +#define DOCTEST_WARN_THROWS_AS_MESSAGE(expr, ex, ...) (static_cast(0)) +#define DOCTEST_CHECK_THROWS_AS_MESSAGE(expr, ex, ...) (static_cast(0)) +#define DOCTEST_REQUIRE_THROWS_AS_MESSAGE(expr, ex, ...) (static_cast(0)) +#define DOCTEST_WARN_THROWS_WITH_MESSAGE(expr, with, ...) (static_cast(0)) +#define DOCTEST_CHECK_THROWS_WITH_MESSAGE(expr, with, ...) (static_cast(0)) +#define DOCTEST_REQUIRE_THROWS_WITH_MESSAGE(expr, with, ...) (static_cast(0)) +#define DOCTEST_WARN_THROWS_WITH_AS_MESSAGE(expr, with, ex, ...) (static_cast(0)) +#define DOCTEST_CHECK_THROWS_WITH_AS_MESSAGE(expr, with, ex, ...) (static_cast(0)) +#define DOCTEST_REQUIRE_THROWS_WITH_AS_MESSAGE(expr, with, ex, ...) (static_cast(0)) +#define DOCTEST_WARN_NOTHROW_MESSAGE(expr, ...) (static_cast(0)) +#define DOCTEST_CHECK_NOTHROW_MESSAGE(expr, ...) (static_cast(0)) +#define DOCTEST_REQUIRE_NOTHROW_MESSAGE(expr, ...) (static_cast(0)) + +#define DOCTEST_WARN_EQ(...) (static_cast(0)) +#define DOCTEST_CHECK_EQ(...) (static_cast(0)) +#define DOCTEST_REQUIRE_EQ(...) (static_cast(0)) +#define DOCTEST_WARN_NE(...) (static_cast(0)) +#define DOCTEST_CHECK_NE(...) (static_cast(0)) +#define DOCTEST_REQUIRE_NE(...) (static_cast(0)) +#define DOCTEST_WARN_GT(...) (static_cast(0)) +#define DOCTEST_CHECK_GT(...) (static_cast(0)) +#define DOCTEST_REQUIRE_GT(...) (static_cast(0)) +#define DOCTEST_WARN_LT(...) (static_cast(0)) +#define DOCTEST_CHECK_LT(...) (static_cast(0)) +#define DOCTEST_REQUIRE_LT(...) (static_cast(0)) +#define DOCTEST_WARN_GE(...) (static_cast(0)) +#define DOCTEST_CHECK_GE(...) (static_cast(0)) +#define DOCTEST_REQUIRE_GE(...) (static_cast(0)) +#define DOCTEST_WARN_LE(...) (static_cast(0)) +#define DOCTEST_CHECK_LE(...) (static_cast(0)) +#define DOCTEST_REQUIRE_LE(...) (static_cast(0)) + +#define DOCTEST_WARN_UNARY(...) (static_cast(0)) +#define DOCTEST_CHECK_UNARY(...) (static_cast(0)) +#define DOCTEST_REQUIRE_UNARY(...) (static_cast(0)) +#define DOCTEST_WARN_UNARY_FALSE(...) (static_cast(0)) +#define DOCTEST_CHECK_UNARY_FALSE(...) (static_cast(0)) +#define DOCTEST_REQUIRE_UNARY_FALSE(...) (static_cast(0)) #endif // DOCTEST_CONFIG_DISABLE @@ -2416,7 +2518,7 @@ int registerReporter(const char* name, int priority, bool isReporter) { #define DOCTEST_FAST_CHECK_UNARY_FALSE DOCTEST_CHECK_UNARY_FALSE #define DOCTEST_FAST_REQUIRE_UNARY_FALSE DOCTEST_REQUIRE_UNARY_FALSE -#define DOCTEST_TEST_CASE_TEMPLATE_INSTANTIATE DOCTEST_TEST_CASE_TEMPLATE_INVOKE +#define DOCTEST_TEST_CASE_TEMPLATE_INSTANTIATE(id, ...) DOCTEST_TEST_CASE_TEMPLATE_INVOKE(id,__VA_ARGS__) // clang-format on // BDD style macros @@ -2436,138 +2538,138 @@ int registerReporter(const char* name, int priority, bool isReporter) { // == SHORT VERSIONS OF THE MACROS #if !defined(DOCTEST_CONFIG_NO_SHORT_MACRO_NAMES) -#define TEST_CASE DOCTEST_TEST_CASE -#define TEST_CASE_CLASS DOCTEST_TEST_CASE_CLASS -#define TEST_CASE_FIXTURE DOCTEST_TEST_CASE_FIXTURE -#define TYPE_TO_STRING DOCTEST_TYPE_TO_STRING -#define TEST_CASE_TEMPLATE DOCTEST_TEST_CASE_TEMPLATE -#define TEST_CASE_TEMPLATE_DEFINE DOCTEST_TEST_CASE_TEMPLATE_DEFINE -#define TEST_CASE_TEMPLATE_INVOKE DOCTEST_TEST_CASE_TEMPLATE_INVOKE -#define TEST_CASE_TEMPLATE_APPLY DOCTEST_TEST_CASE_TEMPLATE_APPLY -#define SUBCASE DOCTEST_SUBCASE -#define TEST_SUITE DOCTEST_TEST_SUITE -#define TEST_SUITE_BEGIN DOCTEST_TEST_SUITE_BEGIN +#define TEST_CASE(name) DOCTEST_TEST_CASE(name) +#define TEST_CASE_CLASS(name) DOCTEST_TEST_CASE_CLASS(name) +#define TEST_CASE_FIXTURE(x, name) DOCTEST_TEST_CASE_FIXTURE(x, name) +#define TYPE_TO_STRING(...) DOCTEST_TYPE_TO_STRING(__VA_ARGS__) +#define TEST_CASE_TEMPLATE(name, T, ...) DOCTEST_TEST_CASE_TEMPLATE(name, T, __VA_ARGS__) +#define TEST_CASE_TEMPLATE_DEFINE(name, T, id) DOCTEST_TEST_CASE_TEMPLATE_DEFINE(name, T, id) +#define TEST_CASE_TEMPLATE_INVOKE(id, ...) DOCTEST_TEST_CASE_TEMPLATE_INVOKE(id, __VA_ARGS__) +#define TEST_CASE_TEMPLATE_APPLY(id, ...) DOCTEST_TEST_CASE_TEMPLATE_APPLY(id, __VA_ARGS__) +#define SUBCASE(name) DOCTEST_SUBCASE(name) +#define TEST_SUITE(decorators) DOCTEST_TEST_SUITE(decorators) +#define TEST_SUITE_BEGIN(name) DOCTEST_TEST_SUITE_BEGIN(name) #define TEST_SUITE_END DOCTEST_TEST_SUITE_END -#define REGISTER_EXCEPTION_TRANSLATOR DOCTEST_REGISTER_EXCEPTION_TRANSLATOR -#define REGISTER_REPORTER DOCTEST_REGISTER_REPORTER -#define REGISTER_LISTENER DOCTEST_REGISTER_LISTENER -#define INFO DOCTEST_INFO -#define CAPTURE DOCTEST_CAPTURE -#define ADD_MESSAGE_AT DOCTEST_ADD_MESSAGE_AT -#define ADD_FAIL_CHECK_AT DOCTEST_ADD_FAIL_CHECK_AT -#define ADD_FAIL_AT DOCTEST_ADD_FAIL_AT -#define MESSAGE DOCTEST_MESSAGE -#define FAIL_CHECK DOCTEST_FAIL_CHECK -#define FAIL DOCTEST_FAIL -#define TO_LVALUE DOCTEST_TO_LVALUE - -#define WARN DOCTEST_WARN -#define WARN_FALSE DOCTEST_WARN_FALSE -#define WARN_THROWS DOCTEST_WARN_THROWS -#define WARN_THROWS_AS DOCTEST_WARN_THROWS_AS -#define WARN_THROWS_WITH DOCTEST_WARN_THROWS_WITH -#define WARN_THROWS_WITH_AS DOCTEST_WARN_THROWS_WITH_AS -#define WARN_NOTHROW DOCTEST_WARN_NOTHROW -#define CHECK DOCTEST_CHECK -#define CHECK_FALSE DOCTEST_CHECK_FALSE -#define CHECK_THROWS DOCTEST_CHECK_THROWS -#define CHECK_THROWS_AS DOCTEST_CHECK_THROWS_AS -#define CHECK_THROWS_WITH DOCTEST_CHECK_THROWS_WITH -#define CHECK_THROWS_WITH_AS DOCTEST_CHECK_THROWS_WITH_AS -#define CHECK_NOTHROW DOCTEST_CHECK_NOTHROW -#define REQUIRE DOCTEST_REQUIRE -#define REQUIRE_FALSE DOCTEST_REQUIRE_FALSE -#define REQUIRE_THROWS DOCTEST_REQUIRE_THROWS -#define REQUIRE_THROWS_AS DOCTEST_REQUIRE_THROWS_AS -#define REQUIRE_THROWS_WITH DOCTEST_REQUIRE_THROWS_WITH -#define REQUIRE_THROWS_WITH_AS DOCTEST_REQUIRE_THROWS_WITH_AS -#define REQUIRE_NOTHROW DOCTEST_REQUIRE_NOTHROW - -#define WARN_MESSAGE DOCTEST_WARN_MESSAGE -#define WARN_FALSE_MESSAGE DOCTEST_WARN_FALSE_MESSAGE -#define WARN_THROWS_MESSAGE DOCTEST_WARN_THROWS_MESSAGE -#define WARN_THROWS_AS_MESSAGE DOCTEST_WARN_THROWS_AS_MESSAGE -#define WARN_THROWS_WITH_MESSAGE DOCTEST_WARN_THROWS_WITH_MESSAGE -#define WARN_THROWS_WITH_AS_MESSAGE DOCTEST_WARN_THROWS_WITH_AS_MESSAGE -#define WARN_NOTHROW_MESSAGE DOCTEST_WARN_NOTHROW_MESSAGE -#define CHECK_MESSAGE DOCTEST_CHECK_MESSAGE -#define CHECK_FALSE_MESSAGE DOCTEST_CHECK_FALSE_MESSAGE -#define CHECK_THROWS_MESSAGE DOCTEST_CHECK_THROWS_MESSAGE -#define CHECK_THROWS_AS_MESSAGE DOCTEST_CHECK_THROWS_AS_MESSAGE -#define CHECK_THROWS_WITH_MESSAGE DOCTEST_CHECK_THROWS_WITH_MESSAGE -#define CHECK_THROWS_WITH_AS_MESSAGE DOCTEST_CHECK_THROWS_WITH_AS_MESSAGE -#define CHECK_NOTHROW_MESSAGE DOCTEST_CHECK_NOTHROW_MESSAGE -#define REQUIRE_MESSAGE DOCTEST_REQUIRE_MESSAGE -#define REQUIRE_FALSE_MESSAGE DOCTEST_REQUIRE_FALSE_MESSAGE -#define REQUIRE_THROWS_MESSAGE DOCTEST_REQUIRE_THROWS_MESSAGE -#define REQUIRE_THROWS_AS_MESSAGE DOCTEST_REQUIRE_THROWS_AS_MESSAGE -#define REQUIRE_THROWS_WITH_MESSAGE DOCTEST_REQUIRE_THROWS_WITH_MESSAGE -#define REQUIRE_THROWS_WITH_AS_MESSAGE DOCTEST_REQUIRE_THROWS_WITH_AS_MESSAGE -#define REQUIRE_NOTHROW_MESSAGE DOCTEST_REQUIRE_NOTHROW_MESSAGE - -#define SCENARIO DOCTEST_SCENARIO -#define SCENARIO_CLASS DOCTEST_SCENARIO_CLASS -#define SCENARIO_TEMPLATE DOCTEST_SCENARIO_TEMPLATE -#define SCENARIO_TEMPLATE_DEFINE DOCTEST_SCENARIO_TEMPLATE_DEFINE -#define GIVEN DOCTEST_GIVEN -#define WHEN DOCTEST_WHEN -#define AND_WHEN DOCTEST_AND_WHEN -#define THEN DOCTEST_THEN -#define AND_THEN DOCTEST_AND_THEN - -#define WARN_EQ DOCTEST_WARN_EQ -#define CHECK_EQ DOCTEST_CHECK_EQ -#define REQUIRE_EQ DOCTEST_REQUIRE_EQ -#define WARN_NE DOCTEST_WARN_NE -#define CHECK_NE DOCTEST_CHECK_NE -#define REQUIRE_NE DOCTEST_REQUIRE_NE -#define WARN_GT DOCTEST_WARN_GT -#define CHECK_GT DOCTEST_CHECK_GT -#define REQUIRE_GT DOCTEST_REQUIRE_GT -#define WARN_LT DOCTEST_WARN_LT -#define CHECK_LT DOCTEST_CHECK_LT -#define REQUIRE_LT DOCTEST_REQUIRE_LT -#define WARN_GE DOCTEST_WARN_GE -#define CHECK_GE DOCTEST_CHECK_GE -#define REQUIRE_GE DOCTEST_REQUIRE_GE -#define WARN_LE DOCTEST_WARN_LE -#define CHECK_LE DOCTEST_CHECK_LE -#define REQUIRE_LE DOCTEST_REQUIRE_LE -#define WARN_UNARY DOCTEST_WARN_UNARY -#define CHECK_UNARY DOCTEST_CHECK_UNARY -#define REQUIRE_UNARY DOCTEST_REQUIRE_UNARY -#define WARN_UNARY_FALSE DOCTEST_WARN_UNARY_FALSE -#define CHECK_UNARY_FALSE DOCTEST_CHECK_UNARY_FALSE -#define REQUIRE_UNARY_FALSE DOCTEST_REQUIRE_UNARY_FALSE +#define REGISTER_EXCEPTION_TRANSLATOR(signature) DOCTEST_REGISTER_EXCEPTION_TRANSLATOR(signature) +#define REGISTER_REPORTER(name, priority, reporter) DOCTEST_REGISTER_REPORTER(name, priority, reporter) +#define REGISTER_LISTENER(name, priority, reporter) DOCTEST_REGISTER_LISTENER(name, priority, reporter) +#define INFO(...) DOCTEST_INFO(__VA_ARGS__) +#define CAPTURE(x) DOCTEST_CAPTURE(x) +#define ADD_MESSAGE_AT(file, line, ...) DOCTEST_ADD_MESSAGE_AT(file, line, __VA_ARGS__) +#define ADD_FAIL_CHECK_AT(file, line, ...) DOCTEST_ADD_FAIL_CHECK_AT(file, line, __VA_ARGS__) +#define ADD_FAIL_AT(file, line, ...) DOCTEST_ADD_FAIL_AT(file, line, __VA_ARGS__) +#define MESSAGE(...) DOCTEST_MESSAGE(__VA_ARGS__) +#define FAIL_CHECK(...) DOCTEST_FAIL_CHECK(__VA_ARGS__) +#define FAIL(...) DOCTEST_FAIL(__VA_ARGS__) +#define TO_LVALUE(...) DOCTEST_TO_LVALUE(__VA_ARGS__) + +#define WARN(...) DOCTEST_WARN(__VA_ARGS__) +#define WARN_FALSE(...) DOCTEST_WARN_FALSE(__VA_ARGS__) +#define WARN_THROWS(...) DOCTEST_WARN_THROWS(__VA_ARGS__) +#define WARN_THROWS_AS(expr, ...) DOCTEST_WARN_THROWS_AS(expr, __VA_ARGS__) +#define WARN_THROWS_WITH(expr, ...) DOCTEST_WARN_THROWS_WITH(expr, __VA_ARGS__) +#define WARN_THROWS_WITH_AS(expr, with, ...) DOCTEST_WARN_THROWS_WITH_AS(expr, with, __VA_ARGS__) +#define WARN_NOTHROW(...) DOCTEST_WARN_NOTHROW(__VA_ARGS__) +#define CHECK(...) DOCTEST_CHECK(__VA_ARGS__) +#define CHECK_FALSE(...) DOCTEST_CHECK_FALSE(__VA_ARGS__) +#define CHECK_THROWS(...) DOCTEST_CHECK_THROWS(__VA_ARGS__) +#define CHECK_THROWS_AS(expr, ...) DOCTEST_CHECK_THROWS_AS(expr, __VA_ARGS__) +#define CHECK_THROWS_WITH(expr, ...) DOCTEST_CHECK_THROWS_WITH(expr, __VA_ARGS__) +#define CHECK_THROWS_WITH_AS(expr, with, ...) DOCTEST_CHECK_THROWS_WITH_AS(expr, with, __VA_ARGS__) +#define CHECK_NOTHROW(...) DOCTEST_CHECK_NOTHROW(__VA_ARGS__) +#define REQUIRE(...) DOCTEST_REQUIRE(__VA_ARGS__) +#define REQUIRE_FALSE(...) DOCTEST_REQUIRE_FALSE(__VA_ARGS__) +#define REQUIRE_THROWS(...) DOCTEST_REQUIRE_THROWS(__VA_ARGS__) +#define REQUIRE_THROWS_AS(expr, ...) DOCTEST_REQUIRE_THROWS_AS(expr, __VA_ARGS__) +#define REQUIRE_THROWS_WITH(expr, ...) DOCTEST_REQUIRE_THROWS_WITH(expr, __VA_ARGS__) +#define REQUIRE_THROWS_WITH_AS(expr, with, ...) DOCTEST_REQUIRE_THROWS_WITH_AS(expr, with, __VA_ARGS__) +#define REQUIRE_NOTHROW(...) DOCTEST_REQUIRE_NOTHROW(__VA_ARGS__) + +#define WARN_MESSAGE(cond, ...) DOCTEST_WARN_MESSAGE(cond, __VA_ARGS__) +#define WARN_FALSE_MESSAGE(cond, ...) DOCTEST_WARN_FALSE_MESSAGE(cond, __VA_ARGS__) +#define WARN_THROWS_MESSAGE(expr, ...) DOCTEST_WARN_THROWS_MESSAGE(expr, __VA_ARGS__) +#define WARN_THROWS_AS_MESSAGE(expr, ex, ...) DOCTEST_WARN_THROWS_AS_MESSAGE(expr, ex, __VA_ARGS__) +#define WARN_THROWS_WITH_MESSAGE(expr, with, ...) DOCTEST_WARN_THROWS_WITH_MESSAGE(expr, with, __VA_ARGS__) +#define WARN_THROWS_WITH_AS_MESSAGE(expr, with, ex, ...) DOCTEST_WARN_THROWS_WITH_AS_MESSAGE(expr, with, ex, __VA_ARGS__) +#define WARN_NOTHROW_MESSAGE(expr, ...) DOCTEST_WARN_NOTHROW_MESSAGE(expr, __VA_ARGS__) +#define CHECK_MESSAGE(cond, ...) DOCTEST_CHECK_MESSAGE(cond, __VA_ARGS__) +#define CHECK_FALSE_MESSAGE(cond, ...) DOCTEST_CHECK_FALSE_MESSAGE(cond, __VA_ARGS__) +#define CHECK_THROWS_MESSAGE(expr, ...) DOCTEST_CHECK_THROWS_MESSAGE(expr, __VA_ARGS__) +#define CHECK_THROWS_AS_MESSAGE(expr, ex, ...) DOCTEST_CHECK_THROWS_AS_MESSAGE(expr, ex, __VA_ARGS__) +#define CHECK_THROWS_WITH_MESSAGE(expr, with, ...) DOCTEST_CHECK_THROWS_WITH_MESSAGE(expr, with, __VA_ARGS__) +#define CHECK_THROWS_WITH_AS_MESSAGE(expr, with, ex, ...) DOCTEST_CHECK_THROWS_WITH_AS_MESSAGE(expr, with, ex, __VA_ARGS__) +#define CHECK_NOTHROW_MESSAGE(expr, ...) DOCTEST_CHECK_NOTHROW_MESSAGE(expr, __VA_ARGS__) +#define REQUIRE_MESSAGE(cond, ...) DOCTEST_REQUIRE_MESSAGE(cond, __VA_ARGS__) +#define REQUIRE_FALSE_MESSAGE(cond, ...) DOCTEST_REQUIRE_FALSE_MESSAGE(cond, __VA_ARGS__) +#define REQUIRE_THROWS_MESSAGE(expr, ...) DOCTEST_REQUIRE_THROWS_MESSAGE(expr, __VA_ARGS__) +#define REQUIRE_THROWS_AS_MESSAGE(expr, ex, ...) DOCTEST_REQUIRE_THROWS_AS_MESSAGE(expr, ex, __VA_ARGS__) +#define REQUIRE_THROWS_WITH_MESSAGE(expr, with, ...) DOCTEST_REQUIRE_THROWS_WITH_MESSAGE(expr, with, __VA_ARGS__) +#define REQUIRE_THROWS_WITH_AS_MESSAGE(expr, with, ex, ...) DOCTEST_REQUIRE_THROWS_WITH_AS_MESSAGE(expr, with, ex, __VA_ARGS__) +#define REQUIRE_NOTHROW_MESSAGE(expr, ...) DOCTEST_REQUIRE_NOTHROW_MESSAGE(expr, __VA_ARGS__) + +#define SCENARIO(name) DOCTEST_SCENARIO(name) +#define SCENARIO_CLASS(name) DOCTEST_SCENARIO_CLASS(name) +#define SCENARIO_TEMPLATE(name, T, ...) DOCTEST_SCENARIO_TEMPLATE(name, T, __VA_ARGS__) +#define SCENARIO_TEMPLATE_DEFINE(name, T, id) DOCTEST_SCENARIO_TEMPLATE_DEFINE(name, T, id) +#define GIVEN(name) DOCTEST_GIVEN(name) +#define WHEN(name) DOCTEST_WHEN(name) +#define AND_WHEN(name) DOCTEST_AND_WHEN(name) +#define THEN(name) DOCTEST_THEN(name) +#define AND_THEN(name) DOCTEST_AND_THEN(name) + +#define WARN_EQ(...) DOCTEST_WARN_EQ(__VA_ARGS__) +#define CHECK_EQ(...) DOCTEST_CHECK_EQ(__VA_ARGS__) +#define REQUIRE_EQ(...) DOCTEST_REQUIRE_EQ(__VA_ARGS__) +#define WARN_NE(...) DOCTEST_WARN_NE(__VA_ARGS__) +#define CHECK_NE(...) DOCTEST_CHECK_NE(__VA_ARGS__) +#define REQUIRE_NE(...) DOCTEST_REQUIRE_NE(__VA_ARGS__) +#define WARN_GT(...) DOCTEST_WARN_GT(__VA_ARGS__) +#define CHECK_GT(...) DOCTEST_CHECK_GT(__VA_ARGS__) +#define REQUIRE_GT(...) DOCTEST_REQUIRE_GT(__VA_ARGS__) +#define WARN_LT(...) DOCTEST_WARN_LT(__VA_ARGS__) +#define CHECK_LT(...) DOCTEST_CHECK_LT(__VA_ARGS__) +#define REQUIRE_LT(...) DOCTEST_REQUIRE_LT(__VA_ARGS__) +#define WARN_GE(...) DOCTEST_WARN_GE(__VA_ARGS__) +#define CHECK_GE(...) DOCTEST_CHECK_GE(__VA_ARGS__) +#define REQUIRE_GE(...) DOCTEST_REQUIRE_GE(__VA_ARGS__) +#define WARN_LE(...) DOCTEST_WARN_LE(__VA_ARGS__) +#define CHECK_LE(...) DOCTEST_CHECK_LE(__VA_ARGS__) +#define REQUIRE_LE(...) DOCTEST_REQUIRE_LE(__VA_ARGS__) +#define WARN_UNARY(...) DOCTEST_WARN_UNARY(__VA_ARGS__) +#define CHECK_UNARY(...) DOCTEST_CHECK_UNARY(__VA_ARGS__) +#define REQUIRE_UNARY(...) DOCTEST_REQUIRE_UNARY(__VA_ARGS__) +#define WARN_UNARY_FALSE(...) DOCTEST_WARN_UNARY_FALSE(__VA_ARGS__) +#define CHECK_UNARY_FALSE(...) DOCTEST_CHECK_UNARY_FALSE(__VA_ARGS__) +#define REQUIRE_UNARY_FALSE(...) DOCTEST_REQUIRE_UNARY_FALSE(__VA_ARGS__) // KEPT FOR BACKWARDS COMPATIBILITY -#define FAST_WARN_EQ DOCTEST_FAST_WARN_EQ -#define FAST_CHECK_EQ DOCTEST_FAST_CHECK_EQ -#define FAST_REQUIRE_EQ DOCTEST_FAST_REQUIRE_EQ -#define FAST_WARN_NE DOCTEST_FAST_WARN_NE -#define FAST_CHECK_NE DOCTEST_FAST_CHECK_NE -#define FAST_REQUIRE_NE DOCTEST_FAST_REQUIRE_NE -#define FAST_WARN_GT DOCTEST_FAST_WARN_GT -#define FAST_CHECK_GT DOCTEST_FAST_CHECK_GT -#define FAST_REQUIRE_GT DOCTEST_FAST_REQUIRE_GT -#define FAST_WARN_LT DOCTEST_FAST_WARN_LT -#define FAST_CHECK_LT DOCTEST_FAST_CHECK_LT -#define FAST_REQUIRE_LT DOCTEST_FAST_REQUIRE_LT -#define FAST_WARN_GE DOCTEST_FAST_WARN_GE -#define FAST_CHECK_GE DOCTEST_FAST_CHECK_GE -#define FAST_REQUIRE_GE DOCTEST_FAST_REQUIRE_GE -#define FAST_WARN_LE DOCTEST_FAST_WARN_LE -#define FAST_CHECK_LE DOCTEST_FAST_CHECK_LE -#define FAST_REQUIRE_LE DOCTEST_FAST_REQUIRE_LE - -#define FAST_WARN_UNARY DOCTEST_FAST_WARN_UNARY -#define FAST_CHECK_UNARY DOCTEST_FAST_CHECK_UNARY -#define FAST_REQUIRE_UNARY DOCTEST_FAST_REQUIRE_UNARY -#define FAST_WARN_UNARY_FALSE DOCTEST_FAST_WARN_UNARY_FALSE -#define FAST_CHECK_UNARY_FALSE DOCTEST_FAST_CHECK_UNARY_FALSE -#define FAST_REQUIRE_UNARY_FALSE DOCTEST_FAST_REQUIRE_UNARY_FALSE - -#define TEST_CASE_TEMPLATE_INSTANTIATE DOCTEST_TEST_CASE_TEMPLATE_INSTANTIATE +#define FAST_WARN_EQ(...) DOCTEST_FAST_WARN_EQ(__VA_ARGS__) +#define FAST_CHECK_EQ(...) DOCTEST_FAST_CHECK_EQ(__VA_ARGS__) +#define FAST_REQUIRE_EQ(...) DOCTEST_FAST_REQUIRE_EQ(__VA_ARGS__) +#define FAST_WARN_NE(...) DOCTEST_FAST_WARN_NE(__VA_ARGS__) +#define FAST_CHECK_NE(...) DOCTEST_FAST_CHECK_NE(__VA_ARGS__) +#define FAST_REQUIRE_NE(...) DOCTEST_FAST_REQUIRE_NE(__VA_ARGS__) +#define FAST_WARN_GT(...) DOCTEST_FAST_WARN_GT(__VA_ARGS__) +#define FAST_CHECK_GT(...) DOCTEST_FAST_CHECK_GT(__VA_ARGS__) +#define FAST_REQUIRE_GT(...) DOCTEST_FAST_REQUIRE_GT(__VA_ARGS__) +#define FAST_WARN_LT(...) DOCTEST_FAST_WARN_LT(__VA_ARGS__) +#define FAST_CHECK_LT(...) DOCTEST_FAST_CHECK_LT(__VA_ARGS__) +#define FAST_REQUIRE_LT(...) DOCTEST_FAST_REQUIRE_LT(__VA_ARGS__) +#define FAST_WARN_GE(...) DOCTEST_FAST_WARN_GE(__VA_ARGS__) +#define FAST_CHECK_GE(...) DOCTEST_FAST_CHECK_GE(__VA_ARGS__) +#define FAST_REQUIRE_GE(...) DOCTEST_FAST_REQUIRE_GE(__VA_ARGS__) +#define FAST_WARN_LE(...) DOCTEST_FAST_WARN_LE(__VA_ARGS__) +#define FAST_CHECK_LE(...) DOCTEST_FAST_CHECK_LE(__VA_ARGS__) +#define FAST_REQUIRE_LE(...) DOCTEST_FAST_REQUIRE_LE(__VA_ARGS__) + +#define FAST_WARN_UNARY(...) DOCTEST_FAST_WARN_UNARY(__VA_ARGS__) +#define FAST_CHECK_UNARY(...) DOCTEST_FAST_CHECK_UNARY(__VA_ARGS__) +#define FAST_REQUIRE_UNARY(...) DOCTEST_FAST_REQUIRE_UNARY(__VA_ARGS__) +#define FAST_WARN_UNARY_FALSE(...) DOCTEST_FAST_WARN_UNARY_FALSE(__VA_ARGS__) +#define FAST_CHECK_UNARY_FALSE(...) DOCTEST_FAST_CHECK_UNARY_FALSE(__VA_ARGS__) +#define FAST_REQUIRE_UNARY_FALSE(...) DOCTEST_FAST_REQUIRE_UNARY_FALSE(__VA_ARGS__) + +#define TEST_CASE_TEMPLATE_INSTANTIATE(id, ...) DOCTEST_TEST_CASE_TEMPLATE_INSTANTIATE(id, __VA_ARGS__) #endif // DOCTEST_CONFIG_NO_SHORT_MACRO_NAMES @@ -2644,6 +2746,7 @@ DOCTEST_CLANG_SUPPRESS_WARNING("-Wmissing-field-initializers") DOCTEST_CLANG_SUPPRESS_WARNING("-Wc++98-compat") DOCTEST_CLANG_SUPPRESS_WARNING("-Wc++98-compat-pedantic") DOCTEST_CLANG_SUPPRESS_WARNING("-Wunused-member-function") +DOCTEST_CLANG_SUPPRESS_WARNING("-Wnonportable-system-include-path") DOCTEST_GCC_SUPPRESS_WARNING_PUSH DOCTEST_GCC_SUPPRESS_WARNING("-Wunknown-pragmas") @@ -2724,9 +2827,7 @@ DOCTEST_MAKE_STD_HEADERS_CLEAN_FROM_WARNINGS_ON_WALL_BEGIN #include #include #include -#ifdef DOCTEST_CONFIG_POSIX_SIGNALS #include -#endif // DOCTEST_CONFIG_POSIX_SIGNALS #include #include #include @@ -2751,7 +2852,7 @@ DOCTEST_MAKE_STD_HEADERS_CLEAN_FROM_WARNINGS_ON_WALL_BEGIN #ifdef __AFXDLL #include #else -#include +#include #endif #include @@ -2762,6 +2863,12 @@ DOCTEST_MAKE_STD_HEADERS_CLEAN_FROM_WARNINGS_ON_WALL_BEGIN #endif // DOCTEST_PLATFORM_WINDOWS +// this is a fix for https://github.com/onqtam/doctest/issues/348 +// https://mail.gnome.org/archives/xml/2012-January/msg00000.html +#if !defined(HAVE_UNISTD_H) && !defined(STDOUT_FILENO) +#define STDOUT_FILENO fileno(stdout) +#endif // HAVE_UNISTD_H + DOCTEST_MAKE_STD_HEADERS_CLEAN_FROM_WARNINGS_ON_WALL_END // counts the number of elements in a C array @@ -2781,12 +2888,24 @@ DOCTEST_MAKE_STD_HEADERS_CLEAN_FROM_WARNINGS_ON_WALL_END #define DOCTEST_THREAD_LOCAL thread_local #endif +#ifndef DOCTEST_MULTI_LANE_ATOMICS_THREAD_LANES +#define DOCTEST_MULTI_LANE_ATOMICS_THREAD_LANES 32 +#endif + +#ifndef DOCTEST_MULTI_LANE_ATOMICS_CACHE_LINE_SIZE +#define DOCTEST_MULTI_LANE_ATOMICS_CACHE_LINE_SIZE 64 +#endif + #ifdef DOCTEST_CONFIG_NO_UNPREFIXED_OPTIONS #define DOCTEST_OPTIONS_PREFIX_DISPLAY DOCTEST_CONFIG_OPTIONS_PREFIX #else #define DOCTEST_OPTIONS_PREFIX_DISPLAY "" #endif +#if defined(WINAPI_FAMILY) && (WINAPI_FAMILY == WINAPI_FAMILY_APP) +#define DOCTEST_CONFIG_NO_MULTI_LANE_ATOMICS +#endif + namespace doctest { bool is_running_in_test = false; @@ -2913,24 +3032,111 @@ typedef timer_large_integer::type ticks_t; //unsigned int getElapsedMilliseconds() const { // return static_cast(getElapsedMicroseconds() / 1000); //} - double getElapsedSeconds() const { return (getCurrentTicks() - m_ticks) / 1000000.0; } + double getElapsedSeconds() const { return static_cast(getCurrentTicks() - m_ticks) / 1000000.0; } private: ticks_t m_ticks = 0; }; +#ifdef DOCTEST_CONFIG_NO_MULTI_LANE_ATOMICS + template + using AtomicOrMultiLaneAtomic = std::atomic; +#else // DOCTEST_CONFIG_NO_MULTI_LANE_ATOMICS + // Provides a multilane implementation of an atomic variable that supports add, sub, load, + // store. Instead of using a single atomic variable, this splits up into multiple ones, + // each sitting on a separate cache line. The goal is to provide a speedup when most + // operations are modifying. It achieves this with two properties: + // + // * Multiple atomics are used, so chance of congestion from the same atomic is reduced. + // * Each atomic sits on a separate cache line, so false sharing is reduced. + // + // The disadvantage is that there is a small overhead due to the use of TLS, and load/store + // is slower because all atomics have to be accessed. + template + class MultiLaneAtomic + { + struct CacheLineAlignedAtomic + { + std::atomic atomic{}; + char padding[DOCTEST_MULTI_LANE_ATOMICS_CACHE_LINE_SIZE - sizeof(std::atomic)]; + }; + CacheLineAlignedAtomic m_atomics[DOCTEST_MULTI_LANE_ATOMICS_THREAD_LANES]; + + static_assert(sizeof(CacheLineAlignedAtomic) == DOCTEST_MULTI_LANE_ATOMICS_CACHE_LINE_SIZE, + "guarantee one atomic takes exactly one cache line"); + + public: + T operator++() DOCTEST_NOEXCEPT { return fetch_add(1) + 1; } + + T operator++(int) DOCTEST_NOEXCEPT { return fetch_add(1); } + + T fetch_add(T arg, std::memory_order order = std::memory_order_seq_cst) DOCTEST_NOEXCEPT { + return myAtomic().fetch_add(arg, order); + } + + T fetch_sub(T arg, std::memory_order order = std::memory_order_seq_cst) DOCTEST_NOEXCEPT { + return myAtomic().fetch_sub(arg, order); + } + + operator T() const DOCTEST_NOEXCEPT { return load(); } + + T load(std::memory_order order = std::memory_order_seq_cst) const DOCTEST_NOEXCEPT { + auto result = T(); + for(auto const& c : m_atomics) { + result += c.atomic.load(order); + } + return result; + } + + T operator=(T desired) DOCTEST_NOEXCEPT { + store(desired); + return desired; + } + + void store(T desired, std::memory_order order = std::memory_order_seq_cst) DOCTEST_NOEXCEPT { + // first value becomes desired", all others become 0. + for(auto& c : m_atomics) { + c.atomic.store(desired, order); + desired = {}; + } + } + + private: + // Each thread has a different atomic that it operates on. If more than NumLanes threads + // use this, some will use the same atomic. So performance will degrate a bit, but still + // everything will work. + // + // The logic here is a bit tricky. The call should be as fast as possible, so that there + // is minimal to no overhead in determining the correct atomic for the current thread. + // + // 1. A global static counter laneCounter counts continuously up. + // 2. Each successive thread will use modulo operation of that counter so it gets an atomic + // assigned in a round-robin fashion. + // 3. This tlsLaneIdx is stored in the thread local data, so it is directly available with + // little overhead. + std::atomic& myAtomic() DOCTEST_NOEXCEPT { + static std::atomic laneCounter; + DOCTEST_THREAD_LOCAL size_t tlsLaneIdx = + laneCounter++ % DOCTEST_MULTI_LANE_ATOMICS_THREAD_LANES; + + return m_atomics[tlsLaneIdx].atomic; + } + }; + + template + using AtomicOrMultiLaneAtomic = MultiLaneAtomic; +#endif // DOCTEST_CONFIG_NO_MULTI_LANE_ATOMICS + // this holds both parameters from the command line and runtime data for tests struct ContextState : ContextOptions, TestRunStats, CurrentTestCaseStats { - std::atomic numAssertsCurrentTest_atomic; - std::atomic numAssertsFailedCurrentTest_atomic; + AtomicOrMultiLaneAtomic numAssertsCurrentTest_atomic; + AtomicOrMultiLaneAtomic numAssertsFailedCurrentTest_atomic; std::vector> filters = decltype(filters)(9); // 9 different filters std::vector reporters_currently_used; - const TestCase* currentTest = nullptr; - assert_handler ah = nullptr; Timer timer; @@ -3031,6 +3237,7 @@ String::String() { String::~String() { if(!isOnStack()) delete[] data.ptr; + // NOLINTNEXTLINE(clang-analyzer-cplusplus.NewDeleteLeaks) } String::String(const char* in) @@ -3039,14 +3246,16 @@ String::String(const char* in) String::String(const char* in, unsigned in_size) { using namespace std; if(in_size <= last) { - memcpy(buf, in, in_size + 1); + memcpy(buf, in, in_size); + buf[in_size] = '\0'; setLast(last - in_size); } else { setOnHeap(); data.size = in_size; data.capacity = data.size + 1; data.ptr = new char[data.capacity]; - memcpy(data.ptr, in, in_size + 1); + memcpy(data.ptr, in, in_size); + data.ptr[in_size] = '\0'; } } @@ -3072,6 +3281,7 @@ String& String::operator+=(const String& other) { if(total_size < len) { // append to the current stack space memcpy(buf + my_old_size, other.c_str(), other_size + 1); + // NOLINTNEXTLINE(clang-analyzer-cplusplus.NewDeleteLeaks) setLast(last - total_size); } else { // alloc new chunk @@ -3113,6 +3323,7 @@ String& String::operator+=(const String& other) { return *this; } +// NOLINTNEXTLINE(clang-analyzer-cplusplus.NewDeleteLeaks) String String::operator+(const String& other) const { return String(*this) += other; } String::String(String&& other) { @@ -3267,6 +3478,7 @@ DOCTEST_CLANG_SUPPRESS_WARNING_WITH_PUSH("-Wnull-dereference") DOCTEST_GCC_SUPPRESS_WARNING_WITH_PUSH("-Wnull-dereference") // depending on the current options this will remove the path of filenames const char* skipPathFromFilename(const char* file) { +#ifndef DOCTEST_CONFIG_DISABLE if(getContextOptions()->no_path_in_filenames) { auto back = std::strrchr(file, '\\'); auto forward = std::strrchr(file, '/'); @@ -3276,6 +3488,7 @@ const char* skipPathFromFilename(const char* file) { return forward + 1; } } +#endif // DOCTEST_CONFIG_DISABLE return file; } DOCTEST_CLANG_SUPPRESS_WARNING_POP @@ -3294,6 +3507,7 @@ IContextScope::~IContextScope() = default; #ifdef DOCTEST_CONFIG_TREAT_CHAR_STAR_AS_STRING String toString(char* in) { return toString(static_cast(in)); } +// NOLINTNEXTLINE(clang-analyzer-cplusplus.NewDeleteLeaks) String toString(const char* in) { return String("\"") + (in ? in : "{null string}") + "\""; } #endif // DOCTEST_CONFIG_TREAT_CHAR_STAR_AS_STRING String toString(bool in) { return in ? "true" : "false"; } @@ -3366,6 +3580,7 @@ bool operator>(double lhs, const Approx& rhs) { return lhs > rhs.m_value && lhs bool operator>(const Approx& lhs, double rhs) { return lhs.m_value > rhs && lhs != rhs; } String toString(const Approx& in) { + // NOLINTNEXTLINE(clang-analyzer-cplusplus.NewDeleteLeaks) return String("Approx( ") + doctest::toString(in.m_value) + " )"; } const ContextOptions* getContextOptions() { return DOCTEST_BRANCH_ON_DISABLED(nullptr, g_cs); } @@ -3411,7 +3626,7 @@ int registerReporter(const char*, int, IReporter*) { return 0; } namespace doctest_detail_test_suite_ns { // holds the current test suite doctest::detail::TestSuite& getCurrentTestSuite() { - static doctest::detail::TestSuite data; + static doctest::detail::TestSuite data{}; return data; } } // namespace doctest_detail_test_suite_ns @@ -3450,7 +3665,7 @@ namespace detail { } #ifndef DOCTEST_CONFIG_NO_EXCEPTIONS - [[noreturn]] void throwException() { + DOCTEST_NORETURN void throwException() { g_cs->shouldLogCurrentException = false; throw TestFailureException(); } // NOLINT(cert-err60-cpp) @@ -3464,8 +3679,8 @@ namespace { // matching of a string against a wildcard mask (case sensitivity configurable) taken from // https://www.codeproject.com/Articles/1088/Wildcard-string-compare-globbing int wildcmp(const char* str, const char* wild, bool caseSensitive) { - const char* cp = nullptr; - const char* mp = nullptr; + const char* cp = str; + const char* mp = wild; while((*str) && (*wild != '*')) { if((caseSensitive ? (*wild != *str) : (tolower(*wild) != tolower(*str))) && @@ -3523,7 +3738,7 @@ namespace detail { Subcase::Subcase(const String& name, const char* file, int line) : m_signature({name, file, line}) { - ContextState* s = g_cs; + auto* s = g_cs; // check subcase filters if(s->subcasesStack.size() < size_t(s->subcase_filter_levels)) { @@ -3565,7 +3780,7 @@ namespace detail { g_cs->subcasesPassed.insert(g_cs->subcasesStack); g_cs->subcasesStack.pop_back(); -#if __cplusplus >= 201703L && defined(__cpp_lib_uncaught_exceptions) && __cpp_lib_uncaught_exceptions >= 201411 +#if defined(__cpp_lib_uncaught_exceptions) && __cpp_lib_uncaught_exceptions >= 201411L && (!defined(__MAC_OS_X_VERSION_MIN_REQUIRED) || __MAC_OS_X_VERSION_MIN_REQUIRED >= 101200) if(std::uncaught_exceptions() > 0 #else if(std::uncaught_exception() @@ -3600,6 +3815,8 @@ namespace detail { // clear state m_description = nullptr; m_skip = false; + m_no_breaks = false; + m_no_output = false; m_may_fail = false; m_should_fail = false; m_expected_failures = 0; @@ -3615,6 +3832,8 @@ namespace detail { m_test_suite = test_suite.m_test_suite; m_description = test_suite.m_description; m_skip = test_suite.m_skip; + m_no_breaks = test_suite.m_no_breaks; + m_no_output = test_suite.m_no_output; m_may_fail = test_suite.m_may_fail; m_should_fail = test_suite.m_should_fail; m_expected_failures = test_suite.m_expected_failures; @@ -3658,25 +3877,31 @@ namespace detail { } bool TestCase::operator<(const TestCase& other) const { + // this will be used only to differentiate between test cases - not relevant for sorting if(m_line != other.m_line) return m_line < other.m_line; - const int file_cmp = std::strcmp(m_file, other.m_file); + const int name_cmp = strcmp(m_name, other.m_name); + if(name_cmp != 0) + return name_cmp < 0; + const int file_cmp = m_file.compare(other.m_file); if(file_cmp != 0) return file_cmp < 0; return m_template_id < other.m_template_id; } + + // all the registered tests + std::set& getRegisteredTests() { + static std::set data; + return data; + } } // namespace detail namespace { using namespace detail; // for sorting tests by file/line bool fileOrderComparator(const TestCase* lhs, const TestCase* rhs) { -#if DOCTEST_MSVC // this is needed because MSVC gives different case for drive letters // for __FILE__ when evaluated in a header and a source file - const int res = doctest::stricmp(lhs->m_file, rhs->m_file); -#else // MSVC - const int res = std::strcmp(lhs->m_file, rhs->m_file); -#endif // MSVC + const int res = lhs->m_file.compare(rhs->m_file, bool(DOCTEST_MSVC)); if(res != 0) return res < 0; if(lhs->m_line != rhs->m_line) @@ -3700,12 +3925,6 @@ namespace { return suiteOrderComparator(lhs, rhs); } - // all the registered tests - std::set& getRegisteredTests() { - static std::set data; - return data; - } - #ifdef DOCTEST_CONFIG_COLORS_WINDOWS HANDLE g_stdoutHandle; WORD g_origFgAttrs; @@ -3731,8 +3950,8 @@ namespace { DOCTEST_CLANG_SUPPRESS_WARNING_WITH_PUSH("-Wdeprecated-declarations") void color_to_stream(std::ostream& s, Color::Enum code) { - ((void)s); // for DOCTEST_CONFIG_COLORS_NONE or DOCTEST_CONFIG_COLORS_WINDOWS - ((void)code); // for DOCTEST_CONFIG_COLORS_NONE + static_cast(s); // for DOCTEST_CONFIG_COLORS_NONE or DOCTEST_CONFIG_COLORS_WINDOWS + static_cast(code); // for DOCTEST_CONFIG_COLORS_NONE #ifdef DOCTEST_CONFIG_COLORS_ANSI if(g_no_colors || (isatty(STDOUT_FILENO) == false && getContextOptions()->force_colors == false)) @@ -3838,7 +4057,28 @@ namespace detail { #ifdef DOCTEST_IS_DEBUGGER_ACTIVE bool isDebuggerActive() { return DOCTEST_IS_DEBUGGER_ACTIVE(); } #else // DOCTEST_IS_DEBUGGER_ACTIVE -#ifdef DOCTEST_PLATFORM_MAC +#ifdef DOCTEST_PLATFORM_LINUX + class ErrnoGuard { + public: + ErrnoGuard() : m_oldErrno(errno) {} + ~ErrnoGuard() { errno = m_oldErrno; } + private: + int m_oldErrno; + }; + // See the comments in Catch2 for the reasoning behind this implementation: + // https://github.com/catchorg/Catch2/blob/v2.13.1/include/internal/catch_debugger.cpp#L79-L102 + bool isDebuggerActive() { + ErrnoGuard guard; + std::ifstream in("/proc/self/status"); + for(std::string line; std::getline(in, line);) { + static const int PREFIX_LEN = 11; + if(line.compare(0, PREFIX_LEN, "TracerPid:\t") == 0) { + return line.length() > PREFIX_LEN && line[PREFIX_LEN] != '0'; + } + } + return false; + } +#elif defined(DOCTEST_PLATFORM_MAC) // The following function is taken directly from the following technical note: // https://developer.apple.com/library/archive/qa/qa1361/_index.html // Returns true if the current process is being debugged (either @@ -3865,7 +4105,7 @@ namespace detail { // We're being debugged if the P_TRACED flag is set. return ((info.kp_proc.p_flag & P_TRACED) != 0); } -#elif DOCTEST_MSVC || defined(__MINGW32__) +#elif DOCTEST_MSVC || defined(__MINGW32__) || defined(__MINGW64__) bool isDebuggerActive() { return ::IsDebuggerPresent() != 0; } #else bool isDebuggerActive() { return false; } @@ -3913,7 +4153,7 @@ namespace detail { // ContextScope has been destroyed (base class destructors run after derived class destructors). // Instead, ContextScope calls this method directly from its destructor. void ContextScopeBase::destroy() { -#if __cplusplus >= 201703L && defined(__cpp_lib_uncaught_exceptions) && __cpp_lib_uncaught_exceptions >= 201411 +#if defined(__cpp_lib_uncaught_exceptions) && __cpp_lib_uncaught_exceptions >= 201411L && (!defined(__MAC_OS_X_VERSION_MIN_REQUIRED) || __MAC_OS_X_VERSION_MIN_REQUIRED >= 101200) if(std::uncaught_exceptions() > 0) { #else if(std::uncaught_exception()) { @@ -3932,19 +4172,12 @@ namespace detail { namespace { using namespace detail; - std::ostream& file_line_to_stream(std::ostream& s, const char* file, int line, - const char* tail = "") { - const auto opt = getContextOptions(); - s << Color::LightGrey << skipPathFromFilename(file) << (opt->gnu_file_line ? ":" : "(") - << (opt->no_line_numbers ? 0 : line) // 0 or the real num depending on the option - << (opt->gnu_file_line ? ":" : "):") << tail; - return s; - } - #if !defined(DOCTEST_CONFIG_POSIX_SIGNALS) && !defined(DOCTEST_CONFIG_WINDOWS_SEH) struct FatalConditionHandler { - void reset() {} + static void reset() {} + static void allocateAltStackMem() {} + static void freeAltStackMem() {} }; #else // DOCTEST_CONFIG_POSIX_SIGNALS || DOCTEST_CONFIG_WINDOWS_SEH @@ -3961,26 +4194,45 @@ namespace { // Windows can easily distinguish between SO and SigSegV, // but SigInt, SigTerm, etc are handled differently. SignalDefs signalDefs[] = { - {EXCEPTION_ILLEGAL_INSTRUCTION, "SIGILL - Illegal instruction signal"}, - {EXCEPTION_STACK_OVERFLOW, "SIGSEGV - Stack overflow"}, - {EXCEPTION_ACCESS_VIOLATION, "SIGSEGV - Segmentation violation signal"}, - {EXCEPTION_INT_DIVIDE_BY_ZERO, "Divide by zero error"}, + {static_cast(EXCEPTION_ILLEGAL_INSTRUCTION), + "SIGILL - Illegal instruction signal"}, + {static_cast(EXCEPTION_STACK_OVERFLOW), "SIGSEGV - Stack overflow"}, + {static_cast(EXCEPTION_ACCESS_VIOLATION), + "SIGSEGV - Segmentation violation signal"}, + {static_cast(EXCEPTION_INT_DIVIDE_BY_ZERO), "Divide by zero error"}, }; struct FatalConditionHandler { static LONG CALLBACK handleException(PEXCEPTION_POINTERS ExceptionInfo) { - for(size_t i = 0; i < DOCTEST_COUNTOF(signalDefs); ++i) { - if(ExceptionInfo->ExceptionRecord->ExceptionCode == signalDefs[i].id) { - reportFatal(signalDefs[i].name); - break; + // Multiple threads may enter this filter/handler at once. We want the error message to be printed on the + // console just once no matter how many threads have crashed. + static std::mutex mutex; + static bool execute = true; + { + std::lock_guard lock(mutex); + if(execute) { + bool reported = false; + for(size_t i = 0; i < DOCTEST_COUNTOF(signalDefs); ++i) { + if(ExceptionInfo->ExceptionRecord->ExceptionCode == signalDefs[i].id) { + reportFatal(signalDefs[i].name); + reported = true; + break; + } + } + if(reported == false) + reportFatal("Unhandled SEH exception caught"); + if(isDebuggerActive() && !g_cs->no_breaks) + DOCTEST_BREAK_INTO_DEBUGGER(); } + execute = false; } - // If its not an exception we care about, pass it along. - // This stops us from eating debugger breaks etc. - return EXCEPTION_CONTINUE_SEARCH; + std::exit(EXIT_FAILURE); } + static void allocateAltStackMem() {} + static void freeAltStackMem() {} + FatalConditionHandler() { isSet = true; // 32k seems enough for doctest to handle stack overflow, @@ -3990,6 +4242,51 @@ namespace { previousTop = SetUnhandledExceptionFilter(handleException); // Pass in guarantee size to be filled SetThreadStackGuarantee(&guaranteeSize); + + // On Windows uncaught exceptions from another thread, exceptions from + // destructors, or calls to std::terminate are not a SEH exception + + // The terminal handler gets called when: + // - std::terminate is called FROM THE TEST RUNNER THREAD + // - an exception is thrown from a destructor FROM THE TEST RUNNER THREAD + original_terminate_handler = std::get_terminate(); + std::set_terminate([]() DOCTEST_NOEXCEPT { + reportFatal("Terminate handler called"); + if(isDebuggerActive() && !g_cs->no_breaks) + DOCTEST_BREAK_INTO_DEBUGGER(); + std::exit(EXIT_FAILURE); // explicitly exit - otherwise the SIGABRT handler may be called as well + }); + + // SIGABRT is raised when: + // - std::terminate is called FROM A DIFFERENT THREAD + // - an exception is thrown from a destructor FROM A DIFFERENT THREAD + // - an uncaught exception is thrown FROM A DIFFERENT THREAD + prev_sigabrt_handler = std::signal(SIGABRT, [](int signal) DOCTEST_NOEXCEPT { + if(signal == SIGABRT) { + reportFatal("SIGABRT - Abort (abnormal termination) signal"); + if(isDebuggerActive() && !g_cs->no_breaks) + DOCTEST_BREAK_INTO_DEBUGGER(); + std::exit(EXIT_FAILURE); + } + }); + + // The following settings are taken from google test, and more + // specifically from UnitTest::Run() inside of gtest.cc + + // the user does not want to see pop-up dialogs about crashes + prev_error_mode_1 = SetErrorMode(SEM_FAILCRITICALERRORS | SEM_NOALIGNMENTFAULTEXCEPT | + SEM_NOGPFAULTERRORBOX | SEM_NOOPENFILEERRORBOX); + // This forces the abort message to go to stderr in all circumstances. + prev_error_mode_2 = _set_error_mode(_OUT_TO_STDERR); + // In the debug version, Visual Studio pops up a separate dialog + // offering a choice to debug the aborted program - we want to disable that. + prev_abort_behavior = _set_abort_behavior(0x0, _WRITE_ABORT_MSG | _CALL_REPORTFAULT); + // In debug mode, the Windows CRT can crash with an assertion over invalid + // input (e.g. passing an invalid file descriptor). The default handling + // for these assertions is to pop up a dialog and wait for user input. + // Instead ask the CRT to dump such assertions to stderr non-interactively. + prev_report_mode = _CrtSetReportMode(_CRT_ASSERT, _CRTDBG_MODE_FILE | _CRTDBG_MODE_DEBUG); + prev_report_file = _CrtSetReportFile(_CRT_ASSERT, _CRTDBG_FILE_STDERR); } static void reset() { @@ -3997,7 +4294,13 @@ namespace { // Unregister handler and restore the old guarantee SetUnhandledExceptionFilter(previousTop); SetThreadStackGuarantee(&guaranteeSize); - previousTop = nullptr; + std::set_terminate(original_terminate_handler); + std::signal(SIGABRT, prev_sigabrt_handler); + SetErrorMode(prev_error_mode_1); + _set_error_mode(prev_error_mode_2); + _set_abort_behavior(prev_abort_behavior, _WRITE_ABORT_MSG | _CALL_REPORTFAULT); + static_cast(_CrtSetReportMode(_CRT_ASSERT, prev_report_mode)); + static_cast(_CrtSetReportFile(_CRT_ASSERT, prev_report_file)); isSet = false; } } @@ -4005,11 +4308,25 @@ namespace { ~FatalConditionHandler() { reset(); } private: + static UINT prev_error_mode_1; + static int prev_error_mode_2; + static unsigned int prev_abort_behavior; + static int prev_report_mode; + static _HFILE prev_report_file; + static void (*prev_sigabrt_handler)(int); + static std::terminate_handler original_terminate_handler; static bool isSet; static ULONG guaranteeSize; static LPTOP_LEVEL_EXCEPTION_FILTER previousTop; }; + UINT FatalConditionHandler::prev_error_mode_1; + int FatalConditionHandler::prev_error_mode_2; + unsigned int FatalConditionHandler::prev_abort_behavior; + int FatalConditionHandler::prev_report_mode; + _HFILE FatalConditionHandler::prev_report_file; + void (*FatalConditionHandler::prev_sigabrt_handler)(int); + std::terminate_handler FatalConditionHandler::original_terminate_handler; bool FatalConditionHandler::isSet = false; ULONG FatalConditionHandler::guaranteeSize = 0; LPTOP_LEVEL_EXCEPTION_FILTER FatalConditionHandler::previousTop = nullptr; @@ -4033,7 +4350,8 @@ namespace { static bool isSet; static struct sigaction oldSigActions[DOCTEST_COUNTOF(signalDefs)]; static stack_t oldSigStack; - static char altStackMem[4 * SIGSTKSZ]; + static size_t altStackSize; + static char* altStackMem; static void handleSignal(int sig) { const char* name = ""; @@ -4049,11 +4367,19 @@ namespace { raise(sig); } + static void allocateAltStackMem() { + altStackMem = new char[altStackSize]; + } + + static void freeAltStackMem() { + delete[] altStackMem; + } + FatalConditionHandler() { isSet = true; stack_t sigStack; sigStack.ss_sp = altStackMem; - sigStack.ss_size = sizeof(altStackMem); + sigStack.ss_size = altStackSize; sigStack.ss_flags = 0; sigaltstack(&sigStack, &oldSigStack); struct sigaction sa = {}; @@ -4078,10 +4404,11 @@ namespace { } }; - bool FatalConditionHandler::isSet = false; + bool FatalConditionHandler::isSet = false; struct sigaction FatalConditionHandler::oldSigActions[DOCTEST_COUNTOF(signalDefs)] = {}; - stack_t FatalConditionHandler::oldSigStack = {}; - char FatalConditionHandler::altStackMem[] = {}; + stack_t FatalConditionHandler::oldSigStack = {}; + size_t FatalConditionHandler::altStackSize = 4 * SIGSTKSZ; + char* FatalConditionHandler::altStackMem = nullptr; #endif // DOCTEST_PLATFORM_WINDOWS #endif // DOCTEST_CONFIG_POSIX_SIGNALS || DOCTEST_CONFIG_WINDOWS_SEH @@ -4183,8 +4510,8 @@ namespace detail { failed_out_of_a_testing_context(*this); } - return m_failed && isDebuggerActive() && - !getContextOptions()->no_breaks; // break into debugger + return m_failed && isDebuggerActive() && !getContextOptions()->no_breaks && + (g_cs->currentTest == nullptr || !g_cs->currentTest->m_no_breaks); // break into debugger } void ResultBuilder::react() const { @@ -4209,6 +4536,7 @@ namespace detail { // ################################################################################### DOCTEST_ASSERT_OUT_OF_TESTS(result.m_decomp); DOCTEST_ASSERT_IN_TESTS(result.m_decomp); + // NOLINTNEXTLINE(clang-analyzer-cplusplus.NewDeleteLeaks) } MessageBuilder::MessageBuilder(const char* file, int line, assertType::Enum severity) { @@ -4233,7 +4561,8 @@ namespace detail { addFailedAssert(m_severity); } - return isDebuggerActive() && !getContextOptions()->no_breaks && !isWarn; // break + return isDebuggerActive() && !getContextOptions()->no_breaks && !isWarn && + (g_cs->currentTest == nullptr || !g_cs->currentTest->m_no_breaks); // break into debugger } void MessageBuilder::react() { @@ -4247,7 +4576,7 @@ namespace { using namespace detail; template - [[noreturn]] void throw_exception(Ex const& e) { + DOCTEST_NORETURN void throw_exception(Ex const& e) { #ifndef DOCTEST_CONFIG_NO_EXCEPTIONS throw e; #else // DOCTEST_CONFIG_NO_EXCEPTIONS @@ -4257,9 +4586,11 @@ namespace { #endif // DOCTEST_CONFIG_NO_EXCEPTIONS } +#ifndef DOCTEST_INTERNAL_ERROR #define DOCTEST_INTERNAL_ERROR(msg) \ throw_exception(std::logic_error( \ __FILE__ ":" DOCTEST_TOSTR(__LINE__) ": Internal doctest error: " msg)) +#endif // DOCTEST_INTERNAL_ERROR // clang-format off @@ -4290,8 +4621,8 @@ namespace { public: ScopedElement( XmlWriter* writer ); - ScopedElement( ScopedElement&& other ) noexcept; - ScopedElement& operator=( ScopedElement&& other ) noexcept; + ScopedElement( ScopedElement&& other ) DOCTEST_NOEXCEPT; + ScopedElement& operator=( ScopedElement&& other ) DOCTEST_NOEXCEPT; ~ScopedElement(); @@ -4508,11 +4839,11 @@ namespace { : m_writer( writer ) {} - XmlWriter::ScopedElement::ScopedElement( ScopedElement&& other ) noexcept + XmlWriter::ScopedElement::ScopedElement( ScopedElement&& other ) DOCTEST_NOEXCEPT : m_writer( other.m_writer ){ other.m_writer = nullptr; } - XmlWriter::ScopedElement& XmlWriter::ScopedElement::operator=( ScopedElement&& other ) noexcept { + XmlWriter::ScopedElement& XmlWriter::ScopedElement::operator=( ScopedElement&& other ) DOCTEST_NOEXCEPT { if ( m_writer ) { m_writer->endElement(); } @@ -4691,7 +5022,7 @@ namespace { tc = ∈ xml.startElement("TestCase") .writeAttribute("name", in.m_name) - .writeAttribute("filename", skipPathFromFilename(in.m_file)) + .writeAttribute("filename", skipPathFromFilename(in.m_file.c_str())) .writeAttribute("line", line(in.m_line)) .writeAttribute("description", in.m_description); @@ -4722,7 +5053,7 @@ namespace { for(unsigned i = 0; i < in.num_data; ++i) { xml.scopedElement("TestCase").writeAttribute("name", in.data[i]->m_name) .writeAttribute("testsuite", in.data[i]->m_test_suite) - .writeAttribute("filename", skipPathFromFilename(in.data[i]->m_file)) + .writeAttribute("filename", skipPathFromFilename(in.data[i]->m_file.c_str())) .writeAttribute("line", line(in.data[i]->m_line)); } xml.scopedElement("OverallResultsTestCases") @@ -4878,6 +5209,279 @@ namespace { DOCTEST_REGISTER_REPORTER("xml", 0, XmlReporter); + void fulltext_log_assert_to_stream(std::ostream& s, const AssertData& rb) { + if((rb.m_at & (assertType::is_throws_as | assertType::is_throws_with)) == + 0) //!OCLINT bitwise operator in conditional + s << Color::Cyan << assertString(rb.m_at) << "( " << rb.m_expr << " ) " + << Color::None; + + if(rb.m_at & assertType::is_throws) { //!OCLINT bitwise operator in conditional + s << (rb.m_threw ? "threw as expected!" : "did NOT throw at all!") << "\n"; + } else if((rb.m_at & assertType::is_throws_as) && + (rb.m_at & assertType::is_throws_with)) { //!OCLINT + s << Color::Cyan << assertString(rb.m_at) << "( " << rb.m_expr << ", \"" + << rb.m_exception_string << "\", " << rb.m_exception_type << " ) " << Color::None; + if(rb.m_threw) { + if(!rb.m_failed) { + s << "threw as expected!\n"; + } else { + s << "threw a DIFFERENT exception! (contents: " << rb.m_exception << ")\n"; + } + } else { + s << "did NOT throw at all!\n"; + } + } else if(rb.m_at & + assertType::is_throws_as) { //!OCLINT bitwise operator in conditional + s << Color::Cyan << assertString(rb.m_at) << "( " << rb.m_expr << ", " + << rb.m_exception_type << " ) " << Color::None + << (rb.m_threw ? (rb.m_threw_as ? "threw as expected!" : + "threw a DIFFERENT exception: ") : + "did NOT throw at all!") + << Color::Cyan << rb.m_exception << "\n"; + } else if(rb.m_at & + assertType::is_throws_with) { //!OCLINT bitwise operator in conditional + s << Color::Cyan << assertString(rb.m_at) << "( " << rb.m_expr << ", \"" + << rb.m_exception_string << "\" ) " << Color::None + << (rb.m_threw ? (!rb.m_failed ? "threw as expected!" : + "threw a DIFFERENT exception: ") : + "did NOT throw at all!") + << Color::Cyan << rb.m_exception << "\n"; + } else if(rb.m_at & assertType::is_nothrow) { //!OCLINT bitwise operator in conditional + s << (rb.m_threw ? "THREW exception: " : "didn't throw!") << Color::Cyan + << rb.m_exception << "\n"; + } else { + s << (rb.m_threw ? "THREW exception: " : + (!rb.m_failed ? "is correct!\n" : "is NOT correct!\n")); + if(rb.m_threw) + s << rb.m_exception << "\n"; + else + s << " values: " << assertString(rb.m_at) << "( " << rb.m_decomp << " )\n"; + } + } + + // TODO: + // - log_message() + // - respond to queries + // - honor remaining options + // - more attributes in tags + struct JUnitReporter : public IReporter + { + XmlWriter xml; + std::mutex mutex; + Timer timer; + std::vector deepestSubcaseStackNames; + + struct JUnitTestCaseData + { + static std::string getCurrentTimestamp() { + // Beware, this is not reentrant because of backward compatibility issues + // Also, UTC only, again because of backward compatibility (%z is C++11) + time_t rawtime; + std::time(&rawtime); + auto const timeStampSize = sizeof("2017-01-16T17:06:45Z"); + + std::tm timeInfo; +#ifdef DOCTEST_PLATFORM_WINDOWS + gmtime_s(&timeInfo, &rawtime); +#else // DOCTEST_PLATFORM_WINDOWS + gmtime_r(&rawtime, &timeInfo); +#endif // DOCTEST_PLATFORM_WINDOWS + + char timeStamp[timeStampSize]; + const char* const fmt = "%Y-%m-%dT%H:%M:%SZ"; + + std::strftime(timeStamp, timeStampSize, fmt, &timeInfo); + return std::string(timeStamp); + } + + struct JUnitTestMessage + { + JUnitTestMessage(const std::string& _message, const std::string& _type, const std::string& _details) + : message(_message), type(_type), details(_details) {} + + JUnitTestMessage(const std::string& _message, const std::string& _details) + : message(_message), type(), details(_details) {} + + std::string message, type, details; + }; + + struct JUnitTestCase + { + JUnitTestCase(const std::string& _classname, const std::string& _name) + : classname(_classname), name(_name), time(0), failures() {} + + std::string classname, name; + double time; + std::vector failures, errors; + }; + + void add(const std::string& classname, const std::string& name) { + testcases.emplace_back(classname, name); + } + + void appendSubcaseNamesToLastTestcase(std::vector nameStack) { + for(auto& curr: nameStack) + if(curr.size()) + testcases.back().name += std::string("/") + curr.c_str(); + } + + void addTime(double time) { + if(time < 1e-4) + time = 0; + testcases.back().time = time; + totalSeconds += time; + } + + void addFailure(const std::string& message, const std::string& type, const std::string& details) { + testcases.back().failures.emplace_back(message, type, details); + ++totalFailures; + } + + void addError(const std::string& message, const std::string& details) { + testcases.back().errors.emplace_back(message, details); + ++totalErrors; + } + + std::vector testcases; + double totalSeconds = 0; + int totalErrors = 0, totalFailures = 0; + }; + + JUnitTestCaseData testCaseData; + + // caching pointers/references to objects of these types - safe to do + const ContextOptions& opt; + const TestCaseData* tc = nullptr; + + JUnitReporter(const ContextOptions& co) + : xml(*co.cout) + , opt(co) {} + + unsigned line(unsigned l) const { return opt.no_line_numbers ? 0 : l; } + + // ========================================================================================= + // WHAT FOLLOWS ARE OVERRIDES OF THE VIRTUAL METHODS OF THE REPORTER INTERFACE + // ========================================================================================= + + void report_query(const QueryData&) override {} + + void test_run_start() override {} + + void test_run_end(const TestRunStats& p) override { + // remove .exe extension - mainly to have the same output on UNIX and Windows + std::string binary_name = skipPathFromFilename(opt.binary_name.c_str()); +#ifdef DOCTEST_PLATFORM_WINDOWS + if(binary_name.rfind(".exe") != std::string::npos) + binary_name = binary_name.substr(0, binary_name.length() - 4); +#endif // DOCTEST_PLATFORM_WINDOWS + xml.startElement("testsuites"); + xml.startElement("testsuite").writeAttribute("name", binary_name) + .writeAttribute("errors", testCaseData.totalErrors) + .writeAttribute("failures", testCaseData.totalFailures) + .writeAttribute("tests", p.numAsserts); + if(opt.no_time_in_output == false) { + xml.writeAttribute("time", testCaseData.totalSeconds); + xml.writeAttribute("timestamp", JUnitTestCaseData::getCurrentTimestamp()); + } + if(opt.no_version == false) + xml.writeAttribute("doctest_version", DOCTEST_VERSION_STR); + + for(const auto& testCase : testCaseData.testcases) { + xml.startElement("testcase") + .writeAttribute("classname", testCase.classname) + .writeAttribute("name", testCase.name); + if(opt.no_time_in_output == false) + xml.writeAttribute("time", testCase.time); + // This is not ideal, but it should be enough to mimic gtest's junit output. + xml.writeAttribute("status", "run"); + + for(const auto& failure : testCase.failures) { + xml.scopedElement("failure") + .writeAttribute("message", failure.message) + .writeAttribute("type", failure.type) + .writeText(failure.details, false); + } + + for(const auto& error : testCase.errors) { + xml.scopedElement("error") + .writeAttribute("message", error.message) + .writeText(error.details); + } + + xml.endElement(); + } + xml.endElement(); + xml.endElement(); + } + + void test_case_start(const TestCaseData& in) override { + testCaseData.add(skipPathFromFilename(in.m_file.c_str()), in.m_name); + timer.start(); + } + + void test_case_reenter(const TestCaseData& in) override { + testCaseData.addTime(timer.getElapsedSeconds()); + testCaseData.appendSubcaseNamesToLastTestcase(deepestSubcaseStackNames); + deepestSubcaseStackNames.clear(); + + timer.start(); + testCaseData.add(skipPathFromFilename(in.m_file.c_str()), in.m_name); + } + + void test_case_end(const CurrentTestCaseStats&) override { + testCaseData.addTime(timer.getElapsedSeconds()); + testCaseData.appendSubcaseNamesToLastTestcase(deepestSubcaseStackNames); + deepestSubcaseStackNames.clear(); + } + + void test_case_exception(const TestCaseException& e) override { + std::lock_guard lock(mutex); + testCaseData.addError("exception", e.error_string.c_str()); + } + + void subcase_start(const SubcaseSignature& in) override { + std::lock_guard lock(mutex); + deepestSubcaseStackNames.push_back(in.m_name); + } + + void subcase_end() override {} + + void log_assert(const AssertData& rb) override { + if(!rb.m_failed) // report only failures & ignore the `success` option + return; + + std::lock_guard lock(mutex); + + std::ostringstream os; + os << skipPathFromFilename(rb.m_file) << (opt.gnu_file_line ? ":" : "(") + << line(rb.m_line) << (opt.gnu_file_line ? ":" : "):") << std::endl; + + fulltext_log_assert_to_stream(os, rb); + log_contexts(os); + testCaseData.addFailure(rb.m_decomp.c_str(), assertString(rb.m_at), os.str()); + } + + void log_message(const MessageData&) override {} + + void test_case_skipped(const TestCaseData&) override {} + + void log_contexts(std::ostringstream& s) { + int num_contexts = get_num_active_contexts(); + if(num_contexts) { + auto contexts = get_active_contexts(); + + s << " logged: "; + for(int i = 0; i < num_contexts; ++i) { + s << (i == 0 ? "" : " "); + contexts[i]->stringify(&s); + s << std::endl; + } + } + } + }; + + DOCTEST_REGISTER_REPORTER("junit", 0, JUnitReporter); + struct Whitespace { int nrSpaces; @@ -4896,6 +5500,7 @@ namespace { std::ostream& s; bool hasLoggedCurrentTestStart; std::vector subcasesStack; + size_t currentSubcaseLevel; std::mutex mutex; // caching pointers/references to objects of these types - safe to do @@ -4954,23 +5559,40 @@ namespace { s << "\n"; } + // this was requested to be made virtual so users could override it + virtual void file_line_to_stream(const char* file, int line, + const char* tail = "") { + s << Color::LightGrey << skipPathFromFilename(file) << (opt.gnu_file_line ? ":" : "(") + << (opt.no_line_numbers ? 0 : line) // 0 or the real num depending on the option + << (opt.gnu_file_line ? ":" : "):") << tail; + } + void logTestStart() { if(hasLoggedCurrentTestStart) return; separator_to_stream(); - file_line_to_stream(s, tc->m_file, tc->m_line, "\n"); + file_line_to_stream(tc->m_file.c_str(), tc->m_line, "\n"); if(tc->m_description) s << Color::Yellow << "DESCRIPTION: " << Color::None << tc->m_description << "\n"; if(tc->m_test_suite && tc->m_test_suite[0] != '\0') s << Color::Yellow << "TEST SUITE: " << Color::None << tc->m_test_suite << "\n"; if(strncmp(tc->m_name, " Scenario:", 11) != 0) - s << Color::None << "TEST CASE: "; + s << Color::Yellow << "TEST CASE: "; s << Color::None << tc->m_name << "\n"; - for(auto& curr : subcasesStack) - if(curr.m_name[0] != '\0') - s << " " << curr.m_name << "\n"; + for(size_t i = 0; i < currentSubcaseLevel; ++i) { + if(subcasesStack[i].m_name[0] != '\0') + s << " " << subcasesStack[i].m_name << "\n"; + } + + if(currentSubcaseLevel != subcasesStack.size()) { + s << Color::Yellow << "\nDEEPEST SUBCASE STACK REACHED (DIFFERENT FROM THE CURRENT ONE):\n" << Color::None; + for(size_t i = 0; i < subcasesStack.size(); ++i) { + if(subcasesStack[i].m_name[0] != '\0') + s << " " << subcasesStack[i].m_name << "\n"; + } + } s << "\n"; @@ -5048,7 +5670,7 @@ namespace { << Whitespace(sizePrefixDisplay*1) << "output filename\n"; s << " -" DOCTEST_OPTIONS_PREFIX_DISPLAY "ob, --" DOCTEST_OPTIONS_PREFIX_DISPLAY "order-by= " << Whitespace(sizePrefixDisplay*1) << "how the tests should be ordered\n"; - s << Whitespace(sizePrefixDisplay*3) << " - by [file/suite/name/rand]\n"; + s << Whitespace(sizePrefixDisplay*3) << " - [file/suite/name/rand/none]\n"; s << " -" DOCTEST_OPTIONS_PREFIX_DISPLAY "rs, --" DOCTEST_OPTIONS_PREFIX_DISPLAY "rand-seed= " << Whitespace(sizePrefixDisplay*1) << "seed for random ordering\n"; s << " -" DOCTEST_OPTIONS_PREFIX_DISPLAY "f, --" DOCTEST_OPTIONS_PREFIX_DISPLAY "first= " @@ -5181,25 +5803,28 @@ namespace { separator_to_stream(); s << std::dec; + auto totwidth = int(std::ceil(log10((std::max(p.numTestCasesPassingFilters, static_cast(p.numAsserts))) + 1))); + auto passwidth = int(std::ceil(log10((std::max(p.numTestCasesPassingFilters - p.numTestCasesFailed, static_cast(p.numAsserts - p.numAssertsFailed))) + 1))); + auto failwidth = int(std::ceil(log10((std::max(p.numTestCasesFailed, static_cast(p.numAssertsFailed))) + 1))); const bool anythingFailed = p.numTestCasesFailed > 0 || p.numAssertsFailed > 0; - s << Color::Cyan << "[doctest] " << Color::None << "test cases: " << std::setw(6) + s << Color::Cyan << "[doctest] " << Color::None << "test cases: " << std::setw(totwidth) << p.numTestCasesPassingFilters << " | " << ((p.numTestCasesPassingFilters == 0 || anythingFailed) ? Color::None : Color::Green) - << std::setw(6) << p.numTestCasesPassingFilters - p.numTestCasesFailed << " passed" + << std::setw(passwidth) << p.numTestCasesPassingFilters - p.numTestCasesFailed << " passed" << Color::None << " | " << (p.numTestCasesFailed > 0 ? Color::Red : Color::None) - << std::setw(6) << p.numTestCasesFailed << " failed" << Color::None << " | "; + << std::setw(failwidth) << p.numTestCasesFailed << " failed" << Color::None << " |"; if(opt.no_skipped_summary == false) { const int numSkipped = p.numTestCases - p.numTestCasesPassingFilters; - s << (numSkipped == 0 ? Color::None : Color::Yellow) << std::setw(6) << numSkipped + s << " " << (numSkipped == 0 ? Color::None : Color::Yellow) << numSkipped << " skipped" << Color::None; } s << "\n"; - s << Color::Cyan << "[doctest] " << Color::None << "assertions: " << std::setw(6) + s << Color::Cyan << "[doctest] " << Color::None << "assertions: " << std::setw(totwidth) << p.numAsserts << " | " << ((p.numAsserts == 0 || anythingFailed) ? Color::None : Color::Green) - << std::setw(6) << (p.numAsserts - p.numAssertsFailed) << " passed" << Color::None - << " | " << (p.numAssertsFailed > 0 ? Color::Red : Color::None) << std::setw(6) + << std::setw(passwidth) << (p.numAsserts - p.numAssertsFailed) << " passed" << Color::None + << " | " << (p.numAssertsFailed > 0 ? Color::Red : Color::None) << std::setw(failwidth) << p.numAssertsFailed << " failed" << Color::None << " |\n"; s << Color::Cyan << "[doctest] " << Color::None << "Status: " << (p.numTestCasesFailed > 0 ? Color::Red : Color::Green) @@ -5209,11 +5834,18 @@ namespace { void test_case_start(const TestCaseData& in) override { hasLoggedCurrentTestStart = false; tc = ∈ + subcasesStack.clear(); + currentSubcaseLevel = 0; } - void test_case_reenter(const TestCaseData&) override {} + void test_case_reenter(const TestCaseData&) override { + subcasesStack.clear(); + } void test_case_end(const CurrentTestCaseStats& st) override { + if(tc->m_no_output) + return; + // log the preamble of the test case only if there is something // else to print - something other than that an assert has failed if(opt.duration || @@ -5248,9 +5880,12 @@ namespace { } void test_case_exception(const TestCaseException& e) override { + if(tc->m_no_output) + return; + logTestStart(); - file_line_to_stream(s, tc->m_file, tc->m_line, " "); + file_line_to_stream(tc->m_file.c_str(), tc->m_line, " "); successOrFailColoredStringToStream(false, e.is_crash ? assertType::is_require : assertType::is_check); s << Color::Red << (e.is_crash ? "test case CRASHED: " : "test case THREW exception: ") @@ -5271,82 +5906,41 @@ namespace { void subcase_start(const SubcaseSignature& subc) override { std::lock_guard lock(mutex); subcasesStack.push_back(subc); + ++currentSubcaseLevel; hasLoggedCurrentTestStart = false; } void subcase_end() override { std::lock_guard lock(mutex); - subcasesStack.pop_back(); + --currentSubcaseLevel; hasLoggedCurrentTestStart = false; } void log_assert(const AssertData& rb) override { - if(!rb.m_failed && !opt.success) + if((!rb.m_failed && !opt.success) || tc->m_no_output) return; std::lock_guard lock(mutex); logTestStart(); - file_line_to_stream(s, rb.m_file, rb.m_line, " "); + file_line_to_stream(rb.m_file, rb.m_line, " "); successOrFailColoredStringToStream(!rb.m_failed, rb.m_at); - if((rb.m_at & (assertType::is_throws_as | assertType::is_throws_with)) == - 0) //!OCLINT bitwise operator in conditional - s << Color::Cyan << assertString(rb.m_at) << "( " << rb.m_expr << " ) " - << Color::None; - - if(rb.m_at & assertType::is_throws) { //!OCLINT bitwise operator in conditional - s << (rb.m_threw ? "threw as expected!" : "did NOT throw at all!") << "\n"; - } else if((rb.m_at & assertType::is_throws_as) && - (rb.m_at & assertType::is_throws_with)) { //!OCLINT - s << Color::Cyan << assertString(rb.m_at) << "( " << rb.m_expr << ", \"" - << rb.m_exception_string << "\", " << rb.m_exception_type << " ) " << Color::None; - if(rb.m_threw) { - if(!rb.m_failed) { - s << "threw as expected!\n"; - } else { - s << "threw a DIFFERENT exception! (contents: " << rb.m_exception << ")\n"; - } - } else { - s << "did NOT throw at all!\n"; - } - } else if(rb.m_at & - assertType::is_throws_as) { //!OCLINT bitwise operator in conditional - s << Color::Cyan << assertString(rb.m_at) << "( " << rb.m_expr << ", " - << rb.m_exception_type << " ) " << Color::None - << (rb.m_threw ? (rb.m_threw_as ? "threw as expected!" : - "threw a DIFFERENT exception: ") : - "did NOT throw at all!") - << Color::Cyan << rb.m_exception << "\n"; - } else if(rb.m_at & - assertType::is_throws_with) { //!OCLINT bitwise operator in conditional - s << Color::Cyan << assertString(rb.m_at) << "( " << rb.m_expr << ", \"" - << rb.m_exception_string << "\" ) " << Color::None - << (rb.m_threw ? (!rb.m_failed ? "threw as expected!" : - "threw a DIFFERENT exception: ") : - "did NOT throw at all!") - << Color::Cyan << rb.m_exception << "\n"; - } else if(rb.m_at & assertType::is_nothrow) { //!OCLINT bitwise operator in conditional - s << (rb.m_threw ? "THREW exception: " : "didn't throw!") << Color::Cyan - << rb.m_exception << "\n"; - } else { - s << (rb.m_threw ? "THREW exception: " : - (!rb.m_failed ? "is correct!\n" : "is NOT correct!\n")); - if(rb.m_threw) - s << rb.m_exception << "\n"; - else - s << " values: " << assertString(rb.m_at) << "( " << rb.m_decomp << " )\n"; - } + + fulltext_log_assert_to_stream(s, rb); log_contexts(); } void log_message(const MessageData& mb) override { + if(tc->m_no_output) + return; + std::lock_guard lock(mutex); logTestStart(); - file_line_to_stream(s, mb.m_file, mb.m_line, " "); + file_line_to_stream(mb.m_file, mb.m_line, " "); s << getSuccessOrFailColor(false, mb.m_severity) << getSuccessOrFailString(mb.m_severity & assertType::is_warn, mb.m_severity, "MESSAGE") << ": "; @@ -5372,8 +5966,10 @@ namespace { bool with_col = g_no_colors; \ g_no_colors = false; \ ConsoleReporter::func(arg); \ - DOCTEST_OUTPUT_DEBUG_STRING(oss.str().c_str()); \ - oss.str(""); \ + if(oss.tellp() != std::streampos{}) { \ + DOCTEST_OUTPUT_DEBUG_STRING(oss.str().c_str()); \ + oss.str(""); \ + } \ g_no_colors = with_col; \ } @@ -5560,7 +6156,7 @@ void Context::parseArgs(int argc, const char* const* argv, bool withDefaults) { #define DOCTEST_PARSE_AS_BOOL_OR_FLAG(name, sname, var, default) \ if(parseIntOption(argc, argv, DOCTEST_CONFIG_OPTIONS_PREFIX name "=", option_bool, intRes) || \ parseIntOption(argc, argv, DOCTEST_CONFIG_OPTIONS_PREFIX sname "=", option_bool, intRes)) \ - p->var = !!intRes; \ + p->var = static_cast(intRes); \ else if(parseFlag(argc, argv, DOCTEST_CONFIG_OPTIONS_PREFIX name) || \ parseFlag(argc, argv, DOCTEST_CONFIG_OPTIONS_PREFIX sname)) \ p->var = true; \ @@ -5606,7 +6202,9 @@ void Context::parseArgs(int argc, const char* const* argv, bool withDefaults) { DOCTEST_PARSE_AS_BOOL_OR_FLAG("gnu-file-line", "gfl", gnu_file_line, !bool(DOCTEST_MSVC)); DOCTEST_PARSE_AS_BOOL_OR_FLAG("no-path-filenames", "npf", no_path_in_filenames, false); DOCTEST_PARSE_AS_BOOL_OR_FLAG("no-line-numbers", "nln", no_line_numbers, false); + DOCTEST_PARSE_AS_BOOL_OR_FLAG("no-debug-output", "ndo", no_debug_output, false); DOCTEST_PARSE_AS_BOOL_OR_FLAG("no-skipped-summary", "nss", no_skipped_summary, false); + DOCTEST_PARSE_AS_BOOL_OR_FLAG("no-time-in-output", "ntio", no_time_in_output, false); // clang-format on if(withDefaults) { @@ -5662,6 +6260,7 @@ void Context::clearFilters() { // allows the user to override procedurally the int/bool options from the command line void Context::setOption(const char* option, int value) { setOption(option, toString(value).c_str()); + // NOLINTNEXTLINE(clang-analyzer-cplusplus.NewDeleteLeaks) } // allows the user to override procedurally the string options from the command line @@ -5702,7 +6301,11 @@ int Context::run() { p->cout = &fstr; } + FatalConditionHandler::allocateAltStackMem(); + auto cleanup_and_return = [&]() { + FatalConditionHandler::freeAltStackMem(); + if(fstr.is_open()) fstr.close(); @@ -5737,7 +6340,7 @@ int Context::run() { p->reporters_currently_used.insert(p->reporters_currently_used.begin(), curr.second(*g_cs)); #ifdef DOCTEST_PLATFORM_WINDOWS - if(isDebuggerActive()) + if(isDebuggerActive() && p->no_debug_output == false) p->reporters_currently_used.push_back(new DebugOutputWindowReporter(*g_cs)); #endif // DOCTEST_PLATFORM_WINDOWS @@ -5774,6 +6377,9 @@ int Context::run() { first[i] = first[idxToSwap]; first[idxToSwap] = temp; } + } else if(p->order_by.compare("none", true) == 0) { + // means no sorting - beneficial for death tests which call into the executable + // with a specific test case in mind - we don't want to slow down the startup times } } @@ -5793,9 +6399,9 @@ int Context::run() { if(tc.m_skip && !p->no_skip) skip_me = true; - if(!matchesAny(tc.m_file, p->filters[0], true, p->case_sensitive)) + if(!matchesAny(tc.m_file.c_str(), p->filters[0], true, p->case_sensitive)) skip_me = true; - if(matchesAny(tc.m_file, p->filters[1], false, p->case_sensitive)) + if(matchesAny(tc.m_file.c_str(), p->filters[1], false, p->case_sensitive)) skip_me = true; if(!matchesAny(tc.m_test_suite, p->filters[2], true, p->case_sensitive)) skip_me = true; @@ -5873,10 +6479,13 @@ int Context::run() { #ifndef DOCTEST_CONFIG_NO_EXCEPTIONS try { #endif // DOCTEST_CONFIG_NO_EXCEPTIONS +// MSVC 2015 diagnoses fatalConditionHandler as unused (because reset() is a static method) +DOCTEST_MSVC_SUPPRESS_WARNING_WITH_PUSH(4101) // unreferenced local variable FatalConditionHandler fatalConditionHandler; // Handle signals // execute the test tc.m_test(); fatalConditionHandler.reset(); +DOCTEST_MSVC_SUPPRESS_WARNING_POP #ifndef DOCTEST_CONFIG_NO_EXCEPTIONS } catch(const TestFailureException&) { p->failure_flags |= TestCaseFailureReason::AssertFailure; @@ -5968,4 +6577,4 @@ DOCTEST_MSVC_SUPPRESS_WARNING_POP DOCTEST_GCC_SUPPRESS_WARNING_POP #endif // DOCTEST_LIBRARY_IMPLEMENTATION -#endif // DOCTEST_CONFIG_IMPLEMENT +#endif // DOCTEST_CONFIG_IMPLEMENT \ No newline at end of file diff --git a/third_party/include/nlohmann/LICENSE.MIT b/third_party/include/nlohmann/LICENSE.MIT index 00599af..f0622d6 100644 --- a/third_party/include/nlohmann/LICENSE.MIT +++ b/third_party/include/nlohmann/LICENSE.MIT @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2013-2017 Niels Lohmann +Copyright (c) 2013-2021 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 diff --git a/third_party/include/nlohmann/json.hpp b/third_party/include/nlohmann/json.hpp index cc822a5..34a8d32 100644 --- a/third_party/include/nlohmann/json.hpp +++ b/third_party/include/nlohmann/json.hpp @@ -1,7 +1,7 @@ /* __ _____ _____ _____ __| | __| | | | JSON for Modern C++ -| | |__ | | | | | | version 3.8.0 +| | |__ | | | | | | version 3.9.1 |_____|_____|_____|_|___| https://github.com/nlohmann/json Licensed under the MIT License . @@ -31,11 +31,10 @@ SOFTWARE. #define INCLUDE_NLOHMANN_JSON_HPP_ #define NLOHMANN_JSON_VERSION_MAJOR 3 -#define NLOHMANN_JSON_VERSION_MINOR 8 -#define NLOHMANN_JSON_VERSION_PATCH 0 +#define NLOHMANN_JSON_VERSION_MINOR 9 +#define NLOHMANN_JSON_VERSION_PATCH 1 #include // all_of, find, for_each -#include // assert #include // nullptr_t, ptrdiff_t, size_t #include // hash, less #include // initializer_list @@ -50,6 +49,7 @@ SOFTWARE. // #include +#include #include // #include @@ -67,57 +67,108 @@ SOFTWARE. #include // pair, declval #include // valarray -// #include - - -// Header is removed in C++20. -// See for more information. - -#if __cplusplus <= 201703L - #include // and, not, or -#endif - // #include #include // exception #include // runtime_error #include // to_string +#include // vector -// #include +// #include +#include // array #include // size_t +#include // uint8_t +#include // string namespace nlohmann { namespace detail { -/// struct to capture the start position of the current token -struct position_t -{ - /// the total number of characters read - std::size_t chars_read_total = 0; - /// the number of characters read in the current line - std::size_t chars_read_current_line = 0; - /// the number of lines read - std::size_t lines_read = 0; +/////////////////////////// +// JSON type enumeration // +/////////////////////////// - /// conversion to size_t to preserve SAX interface - constexpr operator size_t() const - { - return chars_read_total; - } +/*! +@brief the JSON type enumeration + +This enumeration collects the different JSON types. It is internally used to +distinguish the stored values, and the functions @ref basic_json::is_null(), +@ref basic_json::is_object(), @ref basic_json::is_array(), +@ref basic_json::is_string(), @ref basic_json::is_boolean(), +@ref basic_json::is_number() (with @ref basic_json::is_number_integer(), +@ref basic_json::is_number_unsigned(), and @ref basic_json::is_number_float()), +@ref basic_json::is_discarded(), @ref basic_json::is_primitive(), and +@ref basic_json::is_structured() rely on it. + +@note There are three enumeration entries (number_integer, number_unsigned, and +number_float), because the library distinguishes these three types for numbers: +@ref basic_json::number_unsigned_t is used for unsigned integers, +@ref basic_json::number_integer_t is used for signed integers, and +@ref basic_json::number_float_t is used for floating-point numbers or to +approximate integers which do not fit in the limits of their respective type. + +@sa see @ref basic_json::basic_json(const value_t value_type) -- create a JSON +value with the default value for a given type + +@since version 1.0.0 +*/ +enum class value_t : std::uint8_t +{ + null, ///< null value + object, ///< object (unordered set of name/value pairs) + array, ///< array (ordered collection of values) + string, ///< string value + boolean, ///< boolean value + number_integer, ///< number value (signed integer) + number_unsigned, ///< number value (unsigned integer) + number_float, ///< number value (floating-point) + binary, ///< binary array (ordered collection of bytes) + discarded ///< discarded by the parser callback function }; -} // namespace detail -} // namespace nlohmann +/*! +@brief comparison operator for JSON types + +Returns an ordering that is similar to Python: +- order: null < boolean < number < object < array < string < binary +- furthermore, each type is not smaller than itself +- discarded values are not comparable +- binary is represented as a b"" string in python and directly comparable to a + string; however, making a binary array directly comparable with a string would + be surprising behavior in a JSON file. + +@since version 1.0.0 +*/ +inline bool operator<(const value_t lhs, const value_t rhs) noexcept +{ + static constexpr std::array order = {{ + 0 /* null */, 3 /* object */, 4 /* array */, 5 /* string */, + 1 /* boolean */, 2 /* integer */, 2 /* unsigned */, 2 /* float */, + 6 /* binary */ + } + }; + + const auto l_index = static_cast(lhs); + const auto r_index = static_cast(rhs); + return l_index < order.size() && r_index < order.size() && order[l_index] < order[r_index]; +} +} // namespace detail +} // namespace nlohmann + +// #include + +#include // #include #include // pair // #include + + /* Hedley - https://nemequ.github.io/hedley * Created by Evan Nemerson * @@ -130,11 +181,11 @@ struct position_t * SPDX-License-Identifier: CC0-1.0 */ -#if !defined(JSON_HEDLEY_VERSION) || (JSON_HEDLEY_VERSION < 13) +#if !defined(JSON_HEDLEY_VERSION) || (JSON_HEDLEY_VERSION < 15) #if defined(JSON_HEDLEY_VERSION) #undef JSON_HEDLEY_VERSION #endif -#define JSON_HEDLEY_VERSION 13 +#define JSON_HEDLEY_VERSION 15 #if defined(JSON_HEDLEY_STRINGIFY_EX) #undef JSON_HEDLEY_STRINGIFY_EX @@ -207,18 +258,18 @@ struct position_t #if defined(JSON_HEDLEY_MSVC_VERSION) #undef JSON_HEDLEY_MSVC_VERSION #endif -#if defined(_MSC_FULL_VER) && (_MSC_FULL_VER >= 140000000) +#if defined(_MSC_FULL_VER) && (_MSC_FULL_VER >= 140000000) && !defined(__ICL) #define JSON_HEDLEY_MSVC_VERSION JSON_HEDLEY_VERSION_ENCODE(_MSC_FULL_VER / 10000000, (_MSC_FULL_VER % 10000000) / 100000, (_MSC_FULL_VER % 100000) / 100) -#elif defined(_MSC_FULL_VER) +#elif defined(_MSC_FULL_VER) && !defined(__ICL) #define JSON_HEDLEY_MSVC_VERSION JSON_HEDLEY_VERSION_ENCODE(_MSC_FULL_VER / 1000000, (_MSC_FULL_VER % 1000000) / 10000, (_MSC_FULL_VER % 10000) / 10) -#elif defined(_MSC_VER) +#elif defined(_MSC_VER) && !defined(__ICL) #define JSON_HEDLEY_MSVC_VERSION JSON_HEDLEY_VERSION_ENCODE(_MSC_VER / 100, _MSC_VER % 100, 0) #endif #if defined(JSON_HEDLEY_MSVC_VERSION_CHECK) #undef JSON_HEDLEY_MSVC_VERSION_CHECK #endif -#if !defined(_MSC_VER) +#if !defined(JSON_HEDLEY_MSVC_VERSION) #define JSON_HEDLEY_MSVC_VERSION_CHECK(major,minor,patch) (0) #elif defined(_MSC_VER) && (_MSC_VER >= 1400) #define JSON_HEDLEY_MSVC_VERSION_CHECK(major,minor,patch) (_MSC_FULL_VER >= ((major * 10000000) + (minor * 100000) + (patch))) @@ -231,9 +282,9 @@ struct position_t #if defined(JSON_HEDLEY_INTEL_VERSION) #undef JSON_HEDLEY_INTEL_VERSION #endif -#if defined(__INTEL_COMPILER) && defined(__INTEL_COMPILER_UPDATE) +#if defined(__INTEL_COMPILER) && defined(__INTEL_COMPILER_UPDATE) && !defined(__ICL) #define JSON_HEDLEY_INTEL_VERSION JSON_HEDLEY_VERSION_ENCODE(__INTEL_COMPILER / 100, __INTEL_COMPILER % 100, __INTEL_COMPILER_UPDATE) -#elif defined(__INTEL_COMPILER) +#elif defined(__INTEL_COMPILER) && !defined(__ICL) #define JSON_HEDLEY_INTEL_VERSION JSON_HEDLEY_VERSION_ENCODE(__INTEL_COMPILER / 100, __INTEL_COMPILER % 100, 0) #endif @@ -246,6 +297,22 @@ struct position_t #define JSON_HEDLEY_INTEL_VERSION_CHECK(major,minor,patch) (0) #endif +#if defined(JSON_HEDLEY_INTEL_CL_VERSION) + #undef JSON_HEDLEY_INTEL_CL_VERSION +#endif +#if defined(__INTEL_COMPILER) && defined(__INTEL_COMPILER_UPDATE) && defined(__ICL) + #define JSON_HEDLEY_INTEL_CL_VERSION JSON_HEDLEY_VERSION_ENCODE(__INTEL_COMPILER, __INTEL_COMPILER_UPDATE, 0) +#endif + +#if defined(JSON_HEDLEY_INTEL_CL_VERSION_CHECK) + #undef JSON_HEDLEY_INTEL_CL_VERSION_CHECK +#endif +#if defined(JSON_HEDLEY_INTEL_CL_VERSION) + #define JSON_HEDLEY_INTEL_CL_VERSION_CHECK(major,minor,patch) (JSON_HEDLEY_INTEL_CL_VERSION >= JSON_HEDLEY_VERSION_ENCODE(major, minor, patch)) +#else + #define JSON_HEDLEY_INTEL_CL_VERSION_CHECK(major,minor,patch) (0) +#endif + #if defined(JSON_HEDLEY_PGI_VERSION) #undef JSON_HEDLEY_PGI_VERSION #endif @@ -485,7 +552,7 @@ struct position_t #if __VER__ > 1000 #define JSON_HEDLEY_IAR_VERSION JSON_HEDLEY_VERSION_ENCODE((__VER__ / 1000000), ((__VER__ / 1000) % 1000), (__VER__ % 1000)) #else - #define JSON_HEDLEY_IAR_VERSION JSON_HEDLEY_VERSION_ENCODE(VER / 100, __VER__ % 100, 0) + #define JSON_HEDLEY_IAR_VERSION JSON_HEDLEY_VERSION_ENCODE(__VER__ / 100, __VER__ % 100, 0) #endif #endif @@ -562,6 +629,22 @@ struct position_t #define JSON_HEDLEY_PELLES_VERSION_CHECK(major,minor,patch) (0) #endif +#if defined(JSON_HEDLEY_MCST_LCC_VERSION) + #undef JSON_HEDLEY_MCST_LCC_VERSION +#endif +#if defined(__LCC__) && defined(__LCC_MINOR__) + #define JSON_HEDLEY_MCST_LCC_VERSION JSON_HEDLEY_VERSION_ENCODE(__LCC__ / 100, __LCC__ % 100, __LCC_MINOR__) +#endif + +#if defined(JSON_HEDLEY_MCST_LCC_VERSION_CHECK) + #undef JSON_HEDLEY_MCST_LCC_VERSION_CHECK +#endif +#if defined(JSON_HEDLEY_MCST_LCC_VERSION) + #define JSON_HEDLEY_MCST_LCC_VERSION_CHECK(major,minor,patch) (JSON_HEDLEY_MCST_LCC_VERSION >= JSON_HEDLEY_VERSION_ENCODE(major, minor, patch)) +#else + #define JSON_HEDLEY_MCST_LCC_VERSION_CHECK(major,minor,patch) (0) +#endif + #if defined(JSON_HEDLEY_GCC_VERSION) #undef JSON_HEDLEY_GCC_VERSION #endif @@ -571,6 +654,7 @@ struct position_t !defined(JSON_HEDLEY_INTEL_VERSION) && \ !defined(JSON_HEDLEY_PGI_VERSION) && \ !defined(JSON_HEDLEY_ARM_VERSION) && \ + !defined(JSON_HEDLEY_CRAY_VERSION) && \ !defined(JSON_HEDLEY_TI_VERSION) && \ !defined(JSON_HEDLEY_TI_ARMCL_VERSION) && \ !defined(JSON_HEDLEY_TI_CL430_VERSION) && \ @@ -578,7 +662,8 @@ struct position_t !defined(JSON_HEDLEY_TI_CL6X_VERSION) && \ !defined(JSON_HEDLEY_TI_CL7X_VERSION) && \ !defined(JSON_HEDLEY_TI_CLPRU_VERSION) && \ - !defined(__COMPCERT__) + !defined(__COMPCERT__) && \ + !defined(JSON_HEDLEY_MCST_LCC_VERSION) #define JSON_HEDLEY_GCC_VERSION JSON_HEDLEY_GNUC_VERSION #endif @@ -594,17 +679,21 @@ struct position_t #if defined(JSON_HEDLEY_HAS_ATTRIBUTE) #undef JSON_HEDLEY_HAS_ATTRIBUTE #endif -#if defined(__has_attribute) - #define JSON_HEDLEY_HAS_ATTRIBUTE(attribute) __has_attribute(attribute) +#if \ + defined(__has_attribute) && \ + ( \ + (!defined(JSON_HEDLEY_IAR_VERSION) || JSON_HEDLEY_IAR_VERSION_CHECK(8,5,9)) \ + ) +# define JSON_HEDLEY_HAS_ATTRIBUTE(attribute) __has_attribute(attribute) #else - #define JSON_HEDLEY_HAS_ATTRIBUTE(attribute) (0) +# define JSON_HEDLEY_HAS_ATTRIBUTE(attribute) (0) #endif #if defined(JSON_HEDLEY_GNUC_HAS_ATTRIBUTE) #undef JSON_HEDLEY_GNUC_HAS_ATTRIBUTE #endif #if defined(__has_attribute) - #define JSON_HEDLEY_GNUC_HAS_ATTRIBUTE(attribute,major,minor,patch) __has_attribute(attribute) + #define JSON_HEDLEY_GNUC_HAS_ATTRIBUTE(attribute,major,minor,patch) JSON_HEDLEY_HAS_ATTRIBUTE(attribute) #else #define JSON_HEDLEY_GNUC_HAS_ATTRIBUTE(attribute,major,minor,patch) JSON_HEDLEY_GNUC_VERSION_CHECK(major,minor,patch) #endif @@ -613,7 +702,7 @@ struct position_t #undef JSON_HEDLEY_GCC_HAS_ATTRIBUTE #endif #if defined(__has_attribute) - #define JSON_HEDLEY_GCC_HAS_ATTRIBUTE(attribute,major,minor,patch) __has_attribute(attribute) + #define JSON_HEDLEY_GCC_HAS_ATTRIBUTE(attribute,major,minor,patch) JSON_HEDLEY_HAS_ATTRIBUTE(attribute) #else #define JSON_HEDLEY_GCC_HAS_ATTRIBUTE(attribute,major,minor,patch) JSON_HEDLEY_GCC_VERSION_CHECK(major,minor,patch) #endif @@ -798,6 +887,72 @@ struct position_t #define JSON_HEDLEY_GCC_HAS_WARNING(warning,major,minor,patch) JSON_HEDLEY_GCC_VERSION_CHECK(major,minor,patch) #endif +#if \ + (defined(__STDC_VERSION__) && (__STDC_VERSION__ >= 199901L)) || \ + defined(__clang__) || \ + JSON_HEDLEY_GCC_VERSION_CHECK(3,0,0) || \ + JSON_HEDLEY_INTEL_VERSION_CHECK(13,0,0) || \ + JSON_HEDLEY_IAR_VERSION_CHECK(8,0,0) || \ + JSON_HEDLEY_PGI_VERSION_CHECK(18,4,0) || \ + JSON_HEDLEY_ARM_VERSION_CHECK(4,1,0) || \ + JSON_HEDLEY_TI_VERSION_CHECK(15,12,0) || \ + JSON_HEDLEY_TI_ARMCL_VERSION_CHECK(4,7,0) || \ + JSON_HEDLEY_TI_CL430_VERSION_CHECK(2,0,1) || \ + JSON_HEDLEY_TI_CL2000_VERSION_CHECK(6,1,0) || \ + JSON_HEDLEY_TI_CL6X_VERSION_CHECK(7,0,0) || \ + JSON_HEDLEY_TI_CL7X_VERSION_CHECK(1,2,0) || \ + JSON_HEDLEY_TI_CLPRU_VERSION_CHECK(2,1,0) || \ + JSON_HEDLEY_CRAY_VERSION_CHECK(5,0,0) || \ + JSON_HEDLEY_TINYC_VERSION_CHECK(0,9,17) || \ + JSON_HEDLEY_SUNPRO_VERSION_CHECK(8,0,0) || \ + (JSON_HEDLEY_IBM_VERSION_CHECK(10,1,0) && defined(__C99_PRAGMA_OPERATOR)) + #define JSON_HEDLEY_PRAGMA(value) _Pragma(#value) +#elif JSON_HEDLEY_MSVC_VERSION_CHECK(15,0,0) + #define JSON_HEDLEY_PRAGMA(value) __pragma(value) +#else + #define JSON_HEDLEY_PRAGMA(value) +#endif + +#if defined(JSON_HEDLEY_DIAGNOSTIC_PUSH) + #undef JSON_HEDLEY_DIAGNOSTIC_PUSH +#endif +#if defined(JSON_HEDLEY_DIAGNOSTIC_POP) + #undef JSON_HEDLEY_DIAGNOSTIC_POP +#endif +#if defined(__clang__) + #define JSON_HEDLEY_DIAGNOSTIC_PUSH _Pragma("clang diagnostic push") + #define JSON_HEDLEY_DIAGNOSTIC_POP _Pragma("clang diagnostic pop") +#elif JSON_HEDLEY_INTEL_VERSION_CHECK(13,0,0) + #define JSON_HEDLEY_DIAGNOSTIC_PUSH _Pragma("warning(push)") + #define JSON_HEDLEY_DIAGNOSTIC_POP _Pragma("warning(pop)") +#elif JSON_HEDLEY_GCC_VERSION_CHECK(4,6,0) + #define JSON_HEDLEY_DIAGNOSTIC_PUSH _Pragma("GCC diagnostic push") + #define JSON_HEDLEY_DIAGNOSTIC_POP _Pragma("GCC diagnostic pop") +#elif \ + JSON_HEDLEY_MSVC_VERSION_CHECK(15,0,0) || \ + JSON_HEDLEY_INTEL_CL_VERSION_CHECK(2021,1,0) + #define JSON_HEDLEY_DIAGNOSTIC_PUSH __pragma(warning(push)) + #define JSON_HEDLEY_DIAGNOSTIC_POP __pragma(warning(pop)) +#elif JSON_HEDLEY_ARM_VERSION_CHECK(5,6,0) + #define JSON_HEDLEY_DIAGNOSTIC_PUSH _Pragma("push") + #define JSON_HEDLEY_DIAGNOSTIC_POP _Pragma("pop") +#elif \ + JSON_HEDLEY_TI_VERSION_CHECK(15,12,0) || \ + JSON_HEDLEY_TI_ARMCL_VERSION_CHECK(5,2,0) || \ + JSON_HEDLEY_TI_CL430_VERSION_CHECK(4,4,0) || \ + JSON_HEDLEY_TI_CL6X_VERSION_CHECK(8,1,0) || \ + JSON_HEDLEY_TI_CL7X_VERSION_CHECK(1,2,0) || \ + JSON_HEDLEY_TI_CLPRU_VERSION_CHECK(2,1,0) + #define JSON_HEDLEY_DIAGNOSTIC_PUSH _Pragma("diag_push") + #define JSON_HEDLEY_DIAGNOSTIC_POP _Pragma("diag_pop") +#elif JSON_HEDLEY_PELLES_VERSION_CHECK(2,90,0) + #define JSON_HEDLEY_DIAGNOSTIC_PUSH _Pragma("warning(push)") + #define JSON_HEDLEY_DIAGNOSTIC_POP _Pragma("warning(pop)") +#else + #define JSON_HEDLEY_DIAGNOSTIC_PUSH + #define JSON_HEDLEY_DIAGNOSTIC_POP +#endif + /* JSON_HEDLEY_DIAGNOSTIC_DISABLE_CPP98_COMPAT_WRAP_ is for HEDLEY INTERNAL USE ONLY. API subject to change without notice. */ #if defined(JSON_HEDLEY_DIAGNOSTIC_DISABLE_CPP98_COMPAT_WRAP_) @@ -806,12 +961,22 @@ struct position_t #if defined(__cplusplus) # if JSON_HEDLEY_HAS_WARNING("-Wc++98-compat") # if JSON_HEDLEY_HAS_WARNING("-Wc++17-extensions") -# define JSON_HEDLEY_DIAGNOSTIC_DISABLE_CPP98_COMPAT_WRAP_(xpr) \ +# if JSON_HEDLEY_HAS_WARNING("-Wc++1z-extensions") +# define JSON_HEDLEY_DIAGNOSTIC_DISABLE_CPP98_COMPAT_WRAP_(xpr) \ + JSON_HEDLEY_DIAGNOSTIC_PUSH \ + _Pragma("clang diagnostic ignored \"-Wc++98-compat\"") \ + _Pragma("clang diagnostic ignored \"-Wc++17-extensions\"") \ + _Pragma("clang diagnostic ignored \"-Wc++1z-extensions\"") \ + xpr \ + JSON_HEDLEY_DIAGNOSTIC_POP +# else +# define JSON_HEDLEY_DIAGNOSTIC_DISABLE_CPP98_COMPAT_WRAP_(xpr) \ JSON_HEDLEY_DIAGNOSTIC_PUSH \ _Pragma("clang diagnostic ignored \"-Wc++98-compat\"") \ _Pragma("clang diagnostic ignored \"-Wc++17-extensions\"") \ xpr \ JSON_HEDLEY_DIAGNOSTIC_POP +# endif # else # define JSON_HEDLEY_DIAGNOSTIC_DISABLE_CPP98_COMPAT_WRAP_(xpr) \ JSON_HEDLEY_DIAGNOSTIC_PUSH \ @@ -876,7 +1041,7 @@ struct position_t # define JSON_HEDLEY_CPP_CAST(T, expr) \ JSON_HEDLEY_DIAGNOSTIC_PUSH \ _Pragma("diag_suppress=Pe137") \ - JSON_HEDLEY_DIAGNOSTIC_POP \ + JSON_HEDLEY_DIAGNOSTIC_POP # else # define JSON_HEDLEY_CPP_CAST(T, expr) ((T) (expr)) # endif @@ -884,70 +1049,6 @@ struct position_t # define JSON_HEDLEY_CPP_CAST(T, expr) (expr) #endif -#if \ - (defined(__STDC_VERSION__) && (__STDC_VERSION__ >= 199901L)) || \ - defined(__clang__) || \ - JSON_HEDLEY_GCC_VERSION_CHECK(3,0,0) || \ - JSON_HEDLEY_INTEL_VERSION_CHECK(13,0,0) || \ - JSON_HEDLEY_IAR_VERSION_CHECK(8,0,0) || \ - JSON_HEDLEY_PGI_VERSION_CHECK(18,4,0) || \ - JSON_HEDLEY_ARM_VERSION_CHECK(4,1,0) || \ - JSON_HEDLEY_TI_VERSION_CHECK(15,12,0) || \ - JSON_HEDLEY_TI_ARMCL_VERSION_CHECK(4,7,0) || \ - JSON_HEDLEY_TI_CL430_VERSION_CHECK(2,0,1) || \ - JSON_HEDLEY_TI_CL2000_VERSION_CHECK(6,1,0) || \ - JSON_HEDLEY_TI_CL6X_VERSION_CHECK(7,0,0) || \ - JSON_HEDLEY_TI_CL7X_VERSION_CHECK(1,2,0) || \ - JSON_HEDLEY_TI_CLPRU_VERSION_CHECK(2,1,0) || \ - JSON_HEDLEY_CRAY_VERSION_CHECK(5,0,0) || \ - JSON_HEDLEY_TINYC_VERSION_CHECK(0,9,17) || \ - JSON_HEDLEY_SUNPRO_VERSION_CHECK(8,0,0) || \ - (JSON_HEDLEY_IBM_VERSION_CHECK(10,1,0) && defined(__C99_PRAGMA_OPERATOR)) - #define JSON_HEDLEY_PRAGMA(value) _Pragma(#value) -#elif JSON_HEDLEY_MSVC_VERSION_CHECK(15,0,0) - #define JSON_HEDLEY_PRAGMA(value) __pragma(value) -#else - #define JSON_HEDLEY_PRAGMA(value) -#endif - -#if defined(JSON_HEDLEY_DIAGNOSTIC_PUSH) - #undef JSON_HEDLEY_DIAGNOSTIC_PUSH -#endif -#if defined(JSON_HEDLEY_DIAGNOSTIC_POP) - #undef JSON_HEDLEY_DIAGNOSTIC_POP -#endif -#if defined(__clang__) - #define JSON_HEDLEY_DIAGNOSTIC_PUSH _Pragma("clang diagnostic push") - #define JSON_HEDLEY_DIAGNOSTIC_POP _Pragma("clang diagnostic pop") -#elif JSON_HEDLEY_INTEL_VERSION_CHECK(13,0,0) - #define JSON_HEDLEY_DIAGNOSTIC_PUSH _Pragma("warning(push)") - #define JSON_HEDLEY_DIAGNOSTIC_POP _Pragma("warning(pop)") -#elif JSON_HEDLEY_GCC_VERSION_CHECK(4,6,0) - #define JSON_HEDLEY_DIAGNOSTIC_PUSH _Pragma("GCC diagnostic push") - #define JSON_HEDLEY_DIAGNOSTIC_POP _Pragma("GCC diagnostic pop") -#elif JSON_HEDLEY_MSVC_VERSION_CHECK(15,0,0) - #define JSON_HEDLEY_DIAGNOSTIC_PUSH __pragma(warning(push)) - #define JSON_HEDLEY_DIAGNOSTIC_POP __pragma(warning(pop)) -#elif JSON_HEDLEY_ARM_VERSION_CHECK(5,6,0) - #define JSON_HEDLEY_DIAGNOSTIC_PUSH _Pragma("push") - #define JSON_HEDLEY_DIAGNOSTIC_POP _Pragma("pop") -#elif \ - JSON_HEDLEY_TI_VERSION_CHECK(15,12,0) || \ - JSON_HEDLEY_TI_ARMCL_VERSION_CHECK(5,2,0) || \ - JSON_HEDLEY_TI_CL430_VERSION_CHECK(4,4,0) || \ - JSON_HEDLEY_TI_CL6X_VERSION_CHECK(8,1,0) || \ - JSON_HEDLEY_TI_CL7X_VERSION_CHECK(1,2,0) || \ - JSON_HEDLEY_TI_CLPRU_VERSION_CHECK(2,1,0) - #define JSON_HEDLEY_DIAGNOSTIC_PUSH _Pragma("diag_push") - #define JSON_HEDLEY_DIAGNOSTIC_POP _Pragma("diag_pop") -#elif JSON_HEDLEY_PELLES_VERSION_CHECK(2,90,0) - #define JSON_HEDLEY_DIAGNOSTIC_PUSH _Pragma("warning(push)") - #define JSON_HEDLEY_DIAGNOSTIC_POP _Pragma("warning(pop)") -#else - #define JSON_HEDLEY_DIAGNOSTIC_PUSH - #define JSON_HEDLEY_DIAGNOSTIC_POP -#endif - #if defined(JSON_HEDLEY_DIAGNOSTIC_DISABLE_DEPRECATED) #undef JSON_HEDLEY_DIAGNOSTIC_DISABLE_DEPRECATED #endif @@ -955,12 +1056,18 @@ struct position_t #define JSON_HEDLEY_DIAGNOSTIC_DISABLE_DEPRECATED _Pragma("clang diagnostic ignored \"-Wdeprecated-declarations\"") #elif JSON_HEDLEY_INTEL_VERSION_CHECK(13,0,0) #define JSON_HEDLEY_DIAGNOSTIC_DISABLE_DEPRECATED _Pragma("warning(disable:1478 1786)") +#elif JSON_HEDLEY_INTEL_CL_VERSION_CHECK(2021,1,0) + #define JSON_HEDLEY_DIAGNOSTIC_DISABLE_DEPRECATED __pragma(warning(disable:1478 1786)) +#elif JSON_HEDLEY_PGI_VERSION_CHECK(20,7,0) + #define JSON_HEDLEY_DIAGNOSTIC_DISABLE_DEPRECATED _Pragma("diag_suppress 1215,1216,1444,1445") #elif JSON_HEDLEY_PGI_VERSION_CHECK(17,10,0) #define JSON_HEDLEY_DIAGNOSTIC_DISABLE_DEPRECATED _Pragma("diag_suppress 1215,1444") #elif JSON_HEDLEY_GCC_VERSION_CHECK(4,3,0) #define JSON_HEDLEY_DIAGNOSTIC_DISABLE_DEPRECATED _Pragma("GCC diagnostic ignored \"-Wdeprecated-declarations\"") #elif JSON_HEDLEY_MSVC_VERSION_CHECK(15,0,0) #define JSON_HEDLEY_DIAGNOSTIC_DISABLE_DEPRECATED __pragma(warning(disable:4996)) +#elif JSON_HEDLEY_MCST_LCC_VERSION_CHECK(1,25,10) + #define JSON_HEDLEY_DIAGNOSTIC_DISABLE_DEPRECATED _Pragma("diag_suppress 1215,1444") #elif \ JSON_HEDLEY_TI_VERSION_CHECK(15,12,0) || \ (JSON_HEDLEY_TI_ARMCL_VERSION_CHECK(4,8,0) && defined(__TI_GNU_ATTRIBUTE_SUPPORT__)) || \ @@ -993,6 +1100,8 @@ struct position_t #define JSON_HEDLEY_DIAGNOSTIC_DISABLE_UNKNOWN_PRAGMAS _Pragma("clang diagnostic ignored \"-Wunknown-pragmas\"") #elif JSON_HEDLEY_INTEL_VERSION_CHECK(13,0,0) #define JSON_HEDLEY_DIAGNOSTIC_DISABLE_UNKNOWN_PRAGMAS _Pragma("warning(disable:161)") +#elif JSON_HEDLEY_INTEL_CL_VERSION_CHECK(2021,1,0) + #define JSON_HEDLEY_DIAGNOSTIC_DISABLE_UNKNOWN_PRAGMAS __pragma(warning(disable:161)) #elif JSON_HEDLEY_PGI_VERSION_CHECK(17,10,0) #define JSON_HEDLEY_DIAGNOSTIC_DISABLE_UNKNOWN_PRAGMAS _Pragma("diag_suppress 1675") #elif JSON_HEDLEY_GCC_VERSION_CHECK(4,3,0) @@ -1009,6 +1118,8 @@ struct position_t #define JSON_HEDLEY_DIAGNOSTIC_DISABLE_UNKNOWN_PRAGMAS _Pragma("diag_suppress 163") #elif JSON_HEDLEY_IAR_VERSION_CHECK(8,0,0) #define JSON_HEDLEY_DIAGNOSTIC_DISABLE_UNKNOWN_PRAGMAS _Pragma("diag_suppress=Pe161") +#elif JSON_HEDLEY_MCST_LCC_VERSION_CHECK(1,25,10) + #define JSON_HEDLEY_DIAGNOSTIC_DISABLE_UNKNOWN_PRAGMAS _Pragma("diag_suppress 161") #else #define JSON_HEDLEY_DIAGNOSTIC_DISABLE_UNKNOWN_PRAGMAS #endif @@ -1022,8 +1133,12 @@ struct position_t #define JSON_HEDLEY_DIAGNOSTIC_DISABLE_UNKNOWN_CPP_ATTRIBUTES _Pragma("GCC diagnostic ignored \"-Wdeprecated-declarations\"") #elif JSON_HEDLEY_INTEL_VERSION_CHECK(17,0,0) #define JSON_HEDLEY_DIAGNOSTIC_DISABLE_UNKNOWN_CPP_ATTRIBUTES _Pragma("warning(disable:1292)") +#elif JSON_HEDLEY_INTEL_CL_VERSION_CHECK(2021,1,0) + #define JSON_HEDLEY_DIAGNOSTIC_DISABLE_UNKNOWN_CPP_ATTRIBUTES __pragma(warning(disable:1292)) #elif JSON_HEDLEY_MSVC_VERSION_CHECK(19,0,0) #define JSON_HEDLEY_DIAGNOSTIC_DISABLE_UNKNOWN_CPP_ATTRIBUTES __pragma(warning(disable:5030)) +#elif JSON_HEDLEY_PGI_VERSION_CHECK(20,7,0) + #define JSON_HEDLEY_DIAGNOSTIC_DISABLE_UNKNOWN_CPP_ATTRIBUTES _Pragma("diag_suppress 1097,1098") #elif JSON_HEDLEY_PGI_VERSION_CHECK(17,10,0) #define JSON_HEDLEY_DIAGNOSTIC_DISABLE_UNKNOWN_CPP_ATTRIBUTES _Pragma("diag_suppress 1097") #elif JSON_HEDLEY_SUNPRO_VERSION_CHECK(5,14,0) && defined(__cplusplus) @@ -1035,6 +1150,8 @@ struct position_t #define JSON_HEDLEY_DIAGNOSTIC_DISABLE_UNKNOWN_CPP_ATTRIBUTES _Pragma("diag_suppress 1173") #elif JSON_HEDLEY_IAR_VERSION_CHECK(8,0,0) #define JSON_HEDLEY_DIAGNOSTIC_DISABLE_UNKNOWN_CPP_ATTRIBUTES _Pragma("diag_suppress=Pe1097") +#elif JSON_HEDLEY_MCST_LCC_VERSION_CHECK(1,25,10) + #define JSON_HEDLEY_DIAGNOSTIC_DISABLE_UNKNOWN_CPP_ATTRIBUTES _Pragma("diag_suppress 1097") #else #define JSON_HEDLEY_DIAGNOSTIC_DISABLE_UNKNOWN_CPP_ATTRIBUTES #endif @@ -1052,20 +1169,34 @@ struct position_t #define JSON_HEDLEY_DIAGNOSTIC_DISABLE_CAST_QUAL #endif +#if defined(JSON_HEDLEY_DIAGNOSTIC_DISABLE_UNUSED_FUNCTION) + #undef JSON_HEDLEY_DIAGNOSTIC_DISABLE_UNUSED_FUNCTION +#endif +#if JSON_HEDLEY_HAS_WARNING("-Wunused-function") + #define JSON_HEDLEY_DIAGNOSTIC_DISABLE_UNUSED_FUNCTION _Pragma("clang diagnostic ignored \"-Wunused-function\"") +#elif JSON_HEDLEY_GCC_VERSION_CHECK(3,4,0) + #define JSON_HEDLEY_DIAGNOSTIC_DISABLE_UNUSED_FUNCTION _Pragma("GCC diagnostic ignored \"-Wunused-function\"") +#elif JSON_HEDLEY_MSVC_VERSION_CHECK(1,0,0) + #define JSON_HEDLEY_DIAGNOSTIC_DISABLE_UNUSED_FUNCTION __pragma(warning(disable:4505)) +#elif JSON_HEDLEY_MCST_LCC_VERSION_CHECK(1,25,10) + #define JSON_HEDLEY_DIAGNOSTIC_DISABLE_UNUSED_FUNCTION _Pragma("diag_suppress 3142") +#else + #define JSON_HEDLEY_DIAGNOSTIC_DISABLE_UNUSED_FUNCTION +#endif + #if defined(JSON_HEDLEY_DEPRECATED) #undef JSON_HEDLEY_DEPRECATED #endif #if defined(JSON_HEDLEY_DEPRECATED_FOR) #undef JSON_HEDLEY_DEPRECATED_FOR #endif -#if JSON_HEDLEY_MSVC_VERSION_CHECK(14,0,0) - #define JSON_HEDLEY_DEPRECATED(since) __declspec(deprecated("Since " # since)) +#if \ + JSON_HEDLEY_MSVC_VERSION_CHECK(14,0,0) || \ + JSON_HEDLEY_INTEL_CL_VERSION_CHECK(2021,1,0) + #define JSON_HEDLEY_DEPRECATED(since) __declspec(deprecated("Since " # since)) #define JSON_HEDLEY_DEPRECATED_FOR(since, replacement) __declspec(deprecated("Since " #since "; use " #replacement)) -#elif defined(__cplusplus) && (__cplusplus >= 201402L) - #define JSON_HEDLEY_DEPRECATED(since) JSON_HEDLEY_DIAGNOSTIC_DISABLE_CPP98_COMPAT_WRAP_([[deprecated("Since " #since)]]) - #define JSON_HEDLEY_DEPRECATED_FOR(since, replacement) JSON_HEDLEY_DIAGNOSTIC_DISABLE_CPP98_COMPAT_WRAP_([[deprecated("Since " #since "; use " #replacement)]]) #elif \ - JSON_HEDLEY_HAS_EXTENSION(attribute_deprecated_with_message) || \ + (JSON_HEDLEY_HAS_EXTENSION(attribute_deprecated_with_message) && !defined(JSON_HEDLEY_IAR_VERSION)) || \ JSON_HEDLEY_GCC_VERSION_CHECK(4,5,0) || \ JSON_HEDLEY_INTEL_VERSION_CHECK(13,0,0) || \ JSON_HEDLEY_ARM_VERSION_CHECK(5,6,0) || \ @@ -1075,9 +1206,13 @@ struct position_t JSON_HEDLEY_TI_ARMCL_VERSION_CHECK(18,1,0) || \ JSON_HEDLEY_TI_CL6X_VERSION_CHECK(8,3,0) || \ JSON_HEDLEY_TI_CL7X_VERSION_CHECK(1,2,0) || \ - JSON_HEDLEY_TI_CLPRU_VERSION_CHECK(2,3,0) + JSON_HEDLEY_TI_CLPRU_VERSION_CHECK(2,3,0) || \ + JSON_HEDLEY_MCST_LCC_VERSION_CHECK(1,25,10) #define JSON_HEDLEY_DEPRECATED(since) __attribute__((__deprecated__("Since " #since))) #define JSON_HEDLEY_DEPRECATED_FOR(since, replacement) __attribute__((__deprecated__("Since " #since "; use " #replacement))) +#elif defined(__cplusplus) && (__cplusplus >= 201402L) + #define JSON_HEDLEY_DEPRECATED(since) JSON_HEDLEY_DIAGNOSTIC_DISABLE_CPP98_COMPAT_WRAP_([[deprecated("Since " #since)]]) + #define JSON_HEDLEY_DEPRECATED_FOR(since, replacement) JSON_HEDLEY_DIAGNOSTIC_DISABLE_CPP98_COMPAT_WRAP_([[deprecated("Since " #since "; use " #replacement)]]) #elif \ JSON_HEDLEY_HAS_ATTRIBUTE(deprecated) || \ JSON_HEDLEY_GCC_VERSION_CHECK(3,1,0) || \ @@ -1092,12 +1227,15 @@ struct position_t (JSON_HEDLEY_TI_CL6X_VERSION_CHECK(7,2,0) && defined(__TI_GNU_ATTRIBUTE_SUPPORT__)) || \ JSON_HEDLEY_TI_CL6X_VERSION_CHECK(7,5,0) || \ JSON_HEDLEY_TI_CL7X_VERSION_CHECK(1,2,0) || \ - JSON_HEDLEY_TI_CLPRU_VERSION_CHECK(2,1,0) + JSON_HEDLEY_TI_CLPRU_VERSION_CHECK(2,1,0) || \ + JSON_HEDLEY_MCST_LCC_VERSION_CHECK(1,25,10) || \ + JSON_HEDLEY_IAR_VERSION_CHECK(8,10,0) #define JSON_HEDLEY_DEPRECATED(since) __attribute__((__deprecated__)) #define JSON_HEDLEY_DEPRECATED_FOR(since, replacement) __attribute__((__deprecated__)) #elif \ JSON_HEDLEY_MSVC_VERSION_CHECK(13,10,0) || \ - JSON_HEDLEY_PELLES_VERSION_CHECK(6,50,0) + JSON_HEDLEY_PELLES_VERSION_CHECK(6,50,0) || \ + JSON_HEDLEY_INTEL_CL_VERSION_CHECK(2021,1,0) #define JSON_HEDLEY_DEPRECATED(since) __declspec(deprecated) #define JSON_HEDLEY_DEPRECATED_FOR(since, replacement) __declspec(deprecated) #elif JSON_HEDLEY_IAR_VERSION_CHECK(8,0,0) @@ -1114,7 +1252,8 @@ struct position_t #if \ JSON_HEDLEY_HAS_ATTRIBUTE(warning) || \ JSON_HEDLEY_GCC_VERSION_CHECK(4,3,0) || \ - JSON_HEDLEY_INTEL_VERSION_CHECK(13,0,0) + JSON_HEDLEY_INTEL_VERSION_CHECK(13,0,0) || \ + JSON_HEDLEY_MCST_LCC_VERSION_CHECK(1,25,10) #define JSON_HEDLEY_UNAVAILABLE(available_since) __attribute__((__warning__("Not available until " #available_since))) #else #define JSON_HEDLEY_UNAVAILABLE(available_since) @@ -1126,13 +1265,7 @@ struct position_t #if defined(JSON_HEDLEY_WARN_UNUSED_RESULT_MSG) #undef JSON_HEDLEY_WARN_UNUSED_RESULT_MSG #endif -#if (JSON_HEDLEY_HAS_CPP_ATTRIBUTE(nodiscard) >= 201907L) - #define JSON_HEDLEY_WARN_UNUSED_RESULT JSON_HEDLEY_DIAGNOSTIC_DISABLE_CPP98_COMPAT_WRAP_([[nodiscard]]) - #define JSON_HEDLEY_WARN_UNUSED_RESULT_MSG(msg) JSON_HEDLEY_DIAGNOSTIC_DISABLE_CPP98_COMPAT_WRAP_([[nodiscard(msg)]]) -#elif JSON_HEDLEY_HAS_CPP_ATTRIBUTE(nodiscard) - #define JSON_HEDLEY_WARN_UNUSED_RESULT JSON_HEDLEY_DIAGNOSTIC_DISABLE_CPP98_COMPAT_WRAP_([[nodiscard]]) - #define JSON_HEDLEY_WARN_UNUSED_RESULT_MSG(msg) JSON_HEDLEY_DIAGNOSTIC_DISABLE_CPP98_COMPAT_WRAP_([[nodiscard]]) -#elif \ +#if \ JSON_HEDLEY_HAS_ATTRIBUTE(warn_unused_result) || \ JSON_HEDLEY_GCC_VERSION_CHECK(3,4,0) || \ JSON_HEDLEY_INTEL_VERSION_CHECK(13,0,0) || \ @@ -1148,9 +1281,16 @@ struct position_t JSON_HEDLEY_TI_CL7X_VERSION_CHECK(1,2,0) || \ JSON_HEDLEY_TI_CLPRU_VERSION_CHECK(2,1,0) || \ (JSON_HEDLEY_SUNPRO_VERSION_CHECK(5,15,0) && defined(__cplusplus)) || \ - JSON_HEDLEY_PGI_VERSION_CHECK(17,10,0) + JSON_HEDLEY_PGI_VERSION_CHECK(17,10,0) || \ + JSON_HEDLEY_MCST_LCC_VERSION_CHECK(1,25,10) #define JSON_HEDLEY_WARN_UNUSED_RESULT __attribute__((__warn_unused_result__)) #define JSON_HEDLEY_WARN_UNUSED_RESULT_MSG(msg) __attribute__((__warn_unused_result__)) +#elif (JSON_HEDLEY_HAS_CPP_ATTRIBUTE(nodiscard) >= 201907L) + #define JSON_HEDLEY_WARN_UNUSED_RESULT JSON_HEDLEY_DIAGNOSTIC_DISABLE_CPP98_COMPAT_WRAP_([[nodiscard]]) + #define JSON_HEDLEY_WARN_UNUSED_RESULT_MSG(msg) JSON_HEDLEY_DIAGNOSTIC_DISABLE_CPP98_COMPAT_WRAP_([[nodiscard(msg)]]) +#elif JSON_HEDLEY_HAS_CPP_ATTRIBUTE(nodiscard) + #define JSON_HEDLEY_WARN_UNUSED_RESULT JSON_HEDLEY_DIAGNOSTIC_DISABLE_CPP98_COMPAT_WRAP_([[nodiscard]]) + #define JSON_HEDLEY_WARN_UNUSED_RESULT_MSG(msg) JSON_HEDLEY_DIAGNOSTIC_DISABLE_CPP98_COMPAT_WRAP_([[nodiscard]]) #elif defined(_Check_return_) /* SAL */ #define JSON_HEDLEY_WARN_UNUSED_RESULT _Check_return_ #define JSON_HEDLEY_WARN_UNUSED_RESULT_MSG(msg) _Check_return_ @@ -1166,7 +1306,8 @@ struct position_t JSON_HEDLEY_HAS_ATTRIBUTE(sentinel) || \ JSON_HEDLEY_GCC_VERSION_CHECK(4,0,0) || \ JSON_HEDLEY_INTEL_VERSION_CHECK(13,0,0) || \ - JSON_HEDLEY_ARM_VERSION_CHECK(5,4,0) + JSON_HEDLEY_ARM_VERSION_CHECK(5,4,0) || \ + JSON_HEDLEY_MCST_LCC_VERSION_CHECK(1,25,10) #define JSON_HEDLEY_SENTINEL(position) __attribute__((__sentinel__(position))) #else #define JSON_HEDLEY_SENTINEL(position) @@ -1177,7 +1318,9 @@ struct position_t #endif #if JSON_HEDLEY_IAR_VERSION_CHECK(8,0,0) #define JSON_HEDLEY_NO_RETURN __noreturn -#elif JSON_HEDLEY_INTEL_VERSION_CHECK(13,0,0) +#elif \ + JSON_HEDLEY_INTEL_VERSION_CHECK(13,0,0) || \ + JSON_HEDLEY_MCST_LCC_VERSION_CHECK(1,25,10) #define JSON_HEDLEY_NO_RETURN __attribute__((__noreturn__)) #elif defined(__STDC_VERSION__) && __STDC_VERSION__ >= 201112L #define JSON_HEDLEY_NO_RETURN _Noreturn @@ -1199,11 +1342,14 @@ struct position_t (JSON_HEDLEY_TI_CL6X_VERSION_CHECK(7,2,0) && defined(__TI_GNU_ATTRIBUTE_SUPPORT__)) || \ JSON_HEDLEY_TI_CL6X_VERSION_CHECK(7,5,0) || \ JSON_HEDLEY_TI_CL7X_VERSION_CHECK(1,2,0) || \ - JSON_HEDLEY_TI_CLPRU_VERSION_CHECK(2,1,0) + JSON_HEDLEY_TI_CLPRU_VERSION_CHECK(2,1,0) || \ + JSON_HEDLEY_IAR_VERSION_CHECK(8,10,0) #define JSON_HEDLEY_NO_RETURN __attribute__((__noreturn__)) #elif JSON_HEDLEY_SUNPRO_VERSION_CHECK(5,10,0) #define JSON_HEDLEY_NO_RETURN _Pragma("does_not_return") -#elif JSON_HEDLEY_MSVC_VERSION_CHECK(13,10,0) +#elif \ + JSON_HEDLEY_MSVC_VERSION_CHECK(13,10,0) || \ + JSON_HEDLEY_INTEL_CL_VERSION_CHECK(2021,1,0) #define JSON_HEDLEY_NO_RETURN __declspec(noreturn) #elif JSON_HEDLEY_TI_CL6X_VERSION_CHECK(6,0,0) && defined(__cplusplus) #define JSON_HEDLEY_NO_RETURN _Pragma("FUNC_NEVER_RETURNS;") @@ -1235,7 +1381,8 @@ struct position_t #endif #if \ JSON_HEDLEY_MSVC_VERSION_CHECK(13,10,0) || \ - JSON_HEDLEY_INTEL_VERSION_CHECK(13,0,0) + JSON_HEDLEY_INTEL_VERSION_CHECK(13,0,0) || \ + JSON_HEDLEY_INTEL_CL_VERSION_CHECK(2021,1,0) #define JSON_HEDLEY_ASSUME(expr) __assume(expr) #elif JSON_HEDLEY_HAS_BUILTIN(__builtin_assume) #define JSON_HEDLEY_ASSUME(expr) __builtin_assume(expr) @@ -1253,7 +1400,9 @@ struct position_t JSON_HEDLEY_GCC_VERSION_CHECK(4,5,0) || \ JSON_HEDLEY_PGI_VERSION_CHECK(18,10,0) || \ JSON_HEDLEY_INTEL_VERSION_CHECK(13,0,0) || \ - JSON_HEDLEY_IBM_VERSION_CHECK(13,1,5) + JSON_HEDLEY_IBM_VERSION_CHECK(13,1,5) || \ + JSON_HEDLEY_CRAY_VERSION_CHECK(10,0,0) || \ + JSON_HEDLEY_MCST_LCC_VERSION_CHECK(1,25,10) #define JSON_HEDLEY_UNREACHABLE() __builtin_unreachable() #elif defined(JSON_HEDLEY_ASSUME) #define JSON_HEDLEY_UNREACHABLE() JSON_HEDLEY_ASSUME(0) @@ -1331,7 +1480,8 @@ JSON_HEDLEY_DIAGNOSTIC_POP (JSON_HEDLEY_TI_CL6X_VERSION_CHECK(7,2,0) && defined(__TI_GNU_ATTRIBUTE_SUPPORT__)) || \ JSON_HEDLEY_TI_CL6X_VERSION_CHECK(7,5,0) || \ JSON_HEDLEY_TI_CL7X_VERSION_CHECK(1,2,0) || \ - JSON_HEDLEY_TI_CLPRU_VERSION_CHECK(2,1,0) + JSON_HEDLEY_TI_CLPRU_VERSION_CHECK(2,1,0) || \ + JSON_HEDLEY_MCST_LCC_VERSION_CHECK(1,25,10) #define JSON_HEDLEY_PRINTF_FORMAT(string_idx,first_to_check) __attribute__((__format__(__printf__, string_idx, first_to_check))) #elif JSON_HEDLEY_PELLES_VERSION_CHECK(6,0,0) #define JSON_HEDLEY_PRINTF_FORMAT(string_idx,first_to_check) __declspec(vaformat(printf,string_idx,first_to_check)) @@ -1367,15 +1517,16 @@ JSON_HEDLEY_DIAGNOSTIC_POP #define JSON_HEDLEY_UNPREDICTABLE(expr) __builtin_unpredictable((expr)) #endif #if \ - JSON_HEDLEY_HAS_BUILTIN(__builtin_expect_with_probability) || \ - JSON_HEDLEY_GCC_VERSION_CHECK(9,0,0) + (JSON_HEDLEY_HAS_BUILTIN(__builtin_expect_with_probability) && !defined(JSON_HEDLEY_PGI_VERSION)) || \ + JSON_HEDLEY_GCC_VERSION_CHECK(9,0,0) || \ + JSON_HEDLEY_MCST_LCC_VERSION_CHECK(1,25,10) # define JSON_HEDLEY_PREDICT(expr, value, probability) __builtin_expect_with_probability( (expr), (value), (probability)) # define JSON_HEDLEY_PREDICT_TRUE(expr, probability) __builtin_expect_with_probability(!!(expr), 1 , (probability)) # define JSON_HEDLEY_PREDICT_FALSE(expr, probability) __builtin_expect_with_probability(!!(expr), 0 , (probability)) # define JSON_HEDLEY_LIKELY(expr) __builtin_expect (!!(expr), 1 ) # define JSON_HEDLEY_UNLIKELY(expr) __builtin_expect (!!(expr), 0 ) #elif \ - JSON_HEDLEY_HAS_BUILTIN(__builtin_expect) || \ + (JSON_HEDLEY_HAS_BUILTIN(__builtin_expect) && !defined(JSON_HEDLEY_INTEL_CL_VERSION)) || \ JSON_HEDLEY_GCC_VERSION_CHECK(3,0,0) || \ JSON_HEDLEY_INTEL_VERSION_CHECK(13,0,0) || \ (JSON_HEDLEY_SUNPRO_VERSION_CHECK(5,15,0) && defined(__cplusplus)) || \ @@ -1389,7 +1540,8 @@ JSON_HEDLEY_DIAGNOSTIC_POP JSON_HEDLEY_TI_CL7X_VERSION_CHECK(1,2,0) || \ JSON_HEDLEY_TI_CLPRU_VERSION_CHECK(2,1,0) || \ JSON_HEDLEY_TINYC_VERSION_CHECK(0,9,27) || \ - JSON_HEDLEY_CRAY_VERSION_CHECK(8,1,0) + JSON_HEDLEY_CRAY_VERSION_CHECK(8,1,0) || \ + JSON_HEDLEY_MCST_LCC_VERSION_CHECK(1,25,10) # define JSON_HEDLEY_PREDICT(expr, expected, probability) \ (((probability) >= 0.9) ? __builtin_expect((expr), (expected)) : (JSON_HEDLEY_STATIC_CAST(void, expected), (expr))) # define JSON_HEDLEY_PREDICT_TRUE(expr, probability) \ @@ -1435,11 +1587,14 @@ JSON_HEDLEY_DIAGNOSTIC_POP (JSON_HEDLEY_TI_CL6X_VERSION_CHECK(7,2,0) && defined(__TI_GNU_ATTRIBUTE_SUPPORT__)) || \ JSON_HEDLEY_TI_CL6X_VERSION_CHECK(7,5,0) || \ JSON_HEDLEY_TI_CL7X_VERSION_CHECK(1,2,0) || \ - JSON_HEDLEY_TI_CLPRU_VERSION_CHECK(2,1,0) + JSON_HEDLEY_TI_CLPRU_VERSION_CHECK(2,1,0) || \ + JSON_HEDLEY_MCST_LCC_VERSION_CHECK(1,25,10) #define JSON_HEDLEY_MALLOC __attribute__((__malloc__)) #elif JSON_HEDLEY_SUNPRO_VERSION_CHECK(5,10,0) #define JSON_HEDLEY_MALLOC _Pragma("returns_new_memory") -#elif JSON_HEDLEY_MSVC_VERSION_CHECK(14, 0, 0) +#elif \ + JSON_HEDLEY_MSVC_VERSION_CHECK(14,0,0) || \ + JSON_HEDLEY_INTEL_CL_VERSION_CHECK(2021,1,0) #define JSON_HEDLEY_MALLOC __declspec(restrict) #else #define JSON_HEDLEY_MALLOC @@ -1466,7 +1621,8 @@ JSON_HEDLEY_DIAGNOSTIC_POP JSON_HEDLEY_TI_CL6X_VERSION_CHECK(7,5,0) || \ JSON_HEDLEY_TI_CL7X_VERSION_CHECK(1,2,0) || \ JSON_HEDLEY_TI_CLPRU_VERSION_CHECK(2,1,0) || \ - JSON_HEDLEY_PGI_VERSION_CHECK(17,10,0) + JSON_HEDLEY_PGI_VERSION_CHECK(17,10,0) || \ + JSON_HEDLEY_MCST_LCC_VERSION_CHECK(1,25,10) # define JSON_HEDLEY_PURE __attribute__((__pure__)) #elif JSON_HEDLEY_SUNPRO_VERSION_CHECK(5,10,0) # define JSON_HEDLEY_PURE _Pragma("does_not_write_global_data") @@ -1502,7 +1658,8 @@ JSON_HEDLEY_DIAGNOSTIC_POP JSON_HEDLEY_TI_CL6X_VERSION_CHECK(7,5,0) || \ JSON_HEDLEY_TI_CL7X_VERSION_CHECK(1,2,0) || \ JSON_HEDLEY_TI_CLPRU_VERSION_CHECK(2,1,0) || \ - JSON_HEDLEY_PGI_VERSION_CHECK(17,10,0) + JSON_HEDLEY_PGI_VERSION_CHECK(17,10,0) || \ + JSON_HEDLEY_MCST_LCC_VERSION_CHECK(1,25,10) #define JSON_HEDLEY_CONST __attribute__((__const__)) #elif \ JSON_HEDLEY_SUNPRO_VERSION_CHECK(5,10,0) @@ -1520,6 +1677,7 @@ JSON_HEDLEY_DIAGNOSTIC_POP JSON_HEDLEY_GCC_VERSION_CHECK(3,1,0) || \ JSON_HEDLEY_MSVC_VERSION_CHECK(14,0,0) || \ JSON_HEDLEY_INTEL_VERSION_CHECK(13,0,0) || \ + JSON_HEDLEY_INTEL_CL_VERSION_CHECK(2021,1,0) || \ JSON_HEDLEY_ARM_VERSION_CHECK(4,1,0) || \ JSON_HEDLEY_IBM_VERSION_CHECK(10,1,0) || \ JSON_HEDLEY_PGI_VERSION_CHECK(17,10,0) || \ @@ -1529,7 +1687,8 @@ JSON_HEDLEY_DIAGNOSTIC_POP JSON_HEDLEY_TI_CL7X_VERSION_CHECK(1,2,0) || \ (JSON_HEDLEY_SUNPRO_VERSION_CHECK(5,14,0) && defined(__cplusplus)) || \ JSON_HEDLEY_IAR_VERSION_CHECK(8,0,0) || \ - defined(__clang__) + defined(__clang__) || \ + JSON_HEDLEY_MCST_LCC_VERSION_CHECK(1,25,10) #define JSON_HEDLEY_RESTRICT __restrict #elif JSON_HEDLEY_SUNPRO_VERSION_CHECK(5,3,0) && !defined(__cplusplus) #define JSON_HEDLEY_RESTRICT _Restrict @@ -1550,13 +1709,15 @@ JSON_HEDLEY_DIAGNOSTIC_POP #define JSON_HEDLEY_INLINE __inline__ #elif \ JSON_HEDLEY_MSVC_VERSION_CHECK(12,0,0) || \ + JSON_HEDLEY_INTEL_CL_VERSION_CHECK(2021,1,0) || \ JSON_HEDLEY_ARM_VERSION_CHECK(4,1,0) || \ JSON_HEDLEY_TI_ARMCL_VERSION_CHECK(5,1,0) || \ JSON_HEDLEY_TI_CL430_VERSION_CHECK(3,1,0) || \ JSON_HEDLEY_TI_CL2000_VERSION_CHECK(6,2,0) || \ JSON_HEDLEY_TI_CL6X_VERSION_CHECK(8,0,0) || \ JSON_HEDLEY_TI_CL7X_VERSION_CHECK(1,2,0) || \ - JSON_HEDLEY_TI_CLPRU_VERSION_CHECK(2,1,0) + JSON_HEDLEY_TI_CLPRU_VERSION_CHECK(2,1,0) || \ + JSON_HEDLEY_MCST_LCC_VERSION_CHECK(1,25,10) #define JSON_HEDLEY_INLINE __inline #else #define JSON_HEDLEY_INLINE @@ -1582,9 +1743,13 @@ JSON_HEDLEY_DIAGNOSTIC_POP (JSON_HEDLEY_TI_CL6X_VERSION_CHECK(7,2,0) && defined(__TI_GNU_ATTRIBUTE_SUPPORT__)) || \ JSON_HEDLEY_TI_CL6X_VERSION_CHECK(7,5,0) || \ JSON_HEDLEY_TI_CL7X_VERSION_CHECK(1,2,0) || \ - JSON_HEDLEY_TI_CLPRU_VERSION_CHECK(2,1,0) + JSON_HEDLEY_TI_CLPRU_VERSION_CHECK(2,1,0) || \ + JSON_HEDLEY_MCST_LCC_VERSION_CHECK(1,25,10) || \ + JSON_HEDLEY_IAR_VERSION_CHECK(8,10,0) # define JSON_HEDLEY_ALWAYS_INLINE __attribute__((__always_inline__)) JSON_HEDLEY_INLINE -#elif JSON_HEDLEY_MSVC_VERSION_CHECK(12,0,0) +#elif \ + JSON_HEDLEY_MSVC_VERSION_CHECK(12,0,0) || \ + JSON_HEDLEY_INTEL_CL_VERSION_CHECK(2021,1,0) # define JSON_HEDLEY_ALWAYS_INLINE __forceinline #elif defined(__cplusplus) && \ ( \ @@ -1622,9 +1787,13 @@ JSON_HEDLEY_DIAGNOSTIC_POP (JSON_HEDLEY_TI_CL6X_VERSION_CHECK(7,2,0) && defined(__TI_GNU_ATTRIBUTE_SUPPORT__)) || \ JSON_HEDLEY_TI_CL6X_VERSION_CHECK(7,5,0) || \ JSON_HEDLEY_TI_CL7X_VERSION_CHECK(1,2,0) || \ - JSON_HEDLEY_TI_CLPRU_VERSION_CHECK(2,1,0) + JSON_HEDLEY_TI_CLPRU_VERSION_CHECK(2,1,0) || \ + JSON_HEDLEY_MCST_LCC_VERSION_CHECK(1,25,10) || \ + JSON_HEDLEY_IAR_VERSION_CHECK(8,10,0) #define JSON_HEDLEY_NEVER_INLINE __attribute__((__noinline__)) -#elif JSON_HEDLEY_MSVC_VERSION_CHECK(13,10,0) +#elif \ + JSON_HEDLEY_MSVC_VERSION_CHECK(13,10,0) || \ + JSON_HEDLEY_INTEL_CL_VERSION_CHECK(2021,1,0) #define JSON_HEDLEY_NEVER_INLINE __declspec(noinline) #elif JSON_HEDLEY_PGI_VERSION_CHECK(10,2,0) #define JSON_HEDLEY_NEVER_INLINE _Pragma("noinline") @@ -1667,7 +1836,8 @@ JSON_HEDLEY_DIAGNOSTIC_POP (JSON_HEDLEY_TI_CL6X_VERSION_CHECK(7,2,0) && defined(__TI_GNU_ATTRIBUTE_SUPPORT__)) || \ JSON_HEDLEY_TI_CL6X_VERSION_CHECK(7,5,0) \ ) \ - ) + ) || \ + JSON_HEDLEY_MCST_LCC_VERSION_CHECK(1,25,10) # define JSON_HEDLEY_PRIVATE __attribute__((__visibility__("hidden"))) # define JSON_HEDLEY_PUBLIC __attribute__((__visibility__("default"))) # else @@ -1683,10 +1853,12 @@ JSON_HEDLEY_DIAGNOSTIC_POP #if \ JSON_HEDLEY_HAS_ATTRIBUTE(nothrow) || \ JSON_HEDLEY_GCC_VERSION_CHECK(3,3,0) || \ - JSON_HEDLEY_INTEL_VERSION_CHECK(13,0,0) + JSON_HEDLEY_INTEL_VERSION_CHECK(13,0,0) || \ + JSON_HEDLEY_MCST_LCC_VERSION_CHECK(1,25,10) #define JSON_HEDLEY_NO_THROW __attribute__((__nothrow__)) #elif \ JSON_HEDLEY_MSVC_VERSION_CHECK(13,1,0) || \ + JSON_HEDLEY_INTEL_CL_VERSION_CHECK(2021,1,0) || \ JSON_HEDLEY_ARM_VERSION_CHECK(4,1,0) #define JSON_HEDLEY_NO_THROW __declspec(nothrow) #else @@ -1698,7 +1870,8 @@ JSON_HEDLEY_DIAGNOSTIC_POP #endif #if \ JSON_HEDLEY_HAS_ATTRIBUTE(fallthrough) || \ - JSON_HEDLEY_GCC_VERSION_CHECK(7,0,0) + JSON_HEDLEY_GCC_VERSION_CHECK(7,0,0) || \ + JSON_HEDLEY_MCST_LCC_VERSION_CHECK(1,25,10) #define JSON_HEDLEY_FALL_THROUGH __attribute__((__fallthrough__)) #elif JSON_HEDLEY_HAS_CPP_ATTRIBUTE_NS(clang,fallthrough) #define JSON_HEDLEY_FALL_THROUGH JSON_HEDLEY_DIAGNOSTIC_DISABLE_CPP98_COMPAT_WRAP_([[clang::fallthrough]]) @@ -1715,7 +1888,8 @@ JSON_HEDLEY_DIAGNOSTIC_POP #endif #if \ JSON_HEDLEY_HAS_ATTRIBUTE(returns_nonnull) || \ - JSON_HEDLEY_GCC_VERSION_CHECK(4,9,0) + JSON_HEDLEY_GCC_VERSION_CHECK(4,9,0) || \ + JSON_HEDLEY_MCST_LCC_VERSION_CHECK(1,25,10) #define JSON_HEDLEY_RETURNS_NON_NULL __attribute__((__returns_nonnull__)) #elif defined(_Ret_notnull_) /* SAL */ #define JSON_HEDLEY_RETURNS_NON_NULL _Ret_notnull_ @@ -1757,7 +1931,8 @@ JSON_HEDLEY_DIAGNOSTIC_POP JSON_HEDLEY_IBM_VERSION_CHECK(13,1,0) || \ JSON_HEDLEY_TI_CL6X_VERSION_CHECK(6,1,0) || \ (JSON_HEDLEY_SUNPRO_VERSION_CHECK(5,10,0) && !defined(__cplusplus)) || \ - JSON_HEDLEY_CRAY_VERSION_CHECK(8,1,0) + JSON_HEDLEY_CRAY_VERSION_CHECK(8,1,0) || \ + JSON_HEDLEY_MCST_LCC_VERSION_CHECK(1,25,10) #define JSON_HEDLEY_IS_CONSTANT(expr) __builtin_constant_p(expr) #endif #if !defined(__cplusplus) @@ -1781,7 +1956,7 @@ JSON_HEDLEY_DIAGNOSTIC_POP !defined(JSON_HEDLEY_SUNPRO_VERSION) && \ !defined(JSON_HEDLEY_PGI_VERSION) && \ !defined(JSON_HEDLEY_IAR_VERSION)) || \ - JSON_HEDLEY_HAS_EXTENSION(c_generic_selections) || \ + (JSON_HEDLEY_HAS_EXTENSION(c_generic_selections) && !defined(JSON_HEDLEY_IAR_VERSION)) || \ JSON_HEDLEY_GCC_VERSION_CHECK(4,9,0) || \ JSON_HEDLEY_INTEL_VERSION_CHECK(17,0,0) || \ JSON_HEDLEY_IBM_VERSION_CHECK(12,1,0) || \ @@ -1851,7 +2026,7 @@ JSON_HEDLEY_DIAGNOSTIC_POP #if \ !defined(__cplusplus) && ( \ (defined(__STDC_VERSION__) && (__STDC_VERSION__ >= 201112L)) || \ - JSON_HEDLEY_HAS_FEATURE(c_static_assert) || \ + (JSON_HEDLEY_HAS_FEATURE(c_static_assert) && !defined(JSON_HEDLEY_INTEL_CL_VERSION)) || \ JSON_HEDLEY_GCC_VERSION_CHECK(6,0,0) || \ JSON_HEDLEY_INTEL_VERSION_CHECK(13,0,0) || \ defined(_Static_assert) \ @@ -1859,7 +2034,8 @@ JSON_HEDLEY_DIAGNOSTIC_POP # define JSON_HEDLEY_STATIC_ASSERT(expr, message) _Static_assert(expr, message) #elif \ (defined(__cplusplus) && (__cplusplus >= 201103L)) || \ - JSON_HEDLEY_MSVC_VERSION_CHECK(16,0,0) + JSON_HEDLEY_MSVC_VERSION_CHECK(16,0,0) || \ + JSON_HEDLEY_INTEL_CL_VERSION_CHECK(2021,1,0) # define JSON_HEDLEY_STATIC_ASSERT(expr, message) JSON_HEDLEY_DIAGNOSTIC_DISABLE_CPP98_COMPAT_WRAP_(static_assert(expr, message)) #else # define JSON_HEDLEY_STATIC_ASSERT(expr, message) @@ -1919,7 +2095,9 @@ JSON_HEDLEY_DIAGNOSTIC_POP JSON_HEDLEY_PGI_VERSION_CHECK(18,4,0) || \ JSON_HEDLEY_INTEL_VERSION_CHECK(13,0,0) # define JSON_HEDLEY_WARNING(msg) JSON_HEDLEY_PRAGMA(GCC warning msg) -#elif JSON_HEDLEY_MSVC_VERSION_CHECK(15,0,0) +#elif \ + JSON_HEDLEY_MSVC_VERSION_CHECK(15,0,0) || \ + JSON_HEDLEY_INTEL_CL_VERSION_CHECK(2021,1,0) # define JSON_HEDLEY_WARNING(msg) JSON_HEDLEY_PRAGMA(message(msg)) #else # define JSON_HEDLEY_WARNING(msg) JSON_HEDLEY_MESSAGE(msg) @@ -1955,8 +2133,10 @@ JSON_HEDLEY_DIAGNOSTIC_POP #if defined(JSON_HEDLEY_FLAGS) #undef JSON_HEDLEY_FLAGS #endif -#if JSON_HEDLEY_HAS_ATTRIBUTE(flag_enum) +#if JSON_HEDLEY_HAS_ATTRIBUTE(flag_enum) && (!defined(__cplusplus) || JSON_HEDLEY_HAS_WARNING("-Wbitfield-enum-conversion")) #define JSON_HEDLEY_FLAGS __attribute__((__flag_enum__)) +#else + #define JSON_HEDLEY_FLAGS #endif #if defined(JSON_HEDLEY_FLAGS_CAST) @@ -1976,7 +2156,9 @@ JSON_HEDLEY_DIAGNOSTIC_POP #if defined(JSON_HEDLEY_EMPTY_BASES) #undef JSON_HEDLEY_EMPTY_BASES #endif -#if JSON_HEDLEY_MSVC_VERSION_CHECK(19,0,23918) && !JSON_HEDLEY_MSVC_VERSION_CHECK(20,0,0) +#if \ + (JSON_HEDLEY_MSVC_VERSION_CHECK(19,0,23918) && !JSON_HEDLEY_MSVC_VERSION_CHECK(20,0,0)) || \ + JSON_HEDLEY_INTEL_CL_VERSION_CHECK(2021,1,0) #define JSON_HEDLEY_EMPTY_BASES __declspec(empty_bases) #else #define JSON_HEDLEY_EMPTY_BASES @@ -2048,17 +2230,20 @@ JSON_HEDLEY_DIAGNOSTIC_POP #endif // C++ language standard detection -#if (defined(__cplusplus) && __cplusplus >= 201703L) || (defined(_HAS_CXX17) && _HAS_CXX17 == 1) // fix for issue #464 - #define JSON_HAS_CPP_17 - #define JSON_HAS_CPP_14 -#elif (defined(__cplusplus) && __cplusplus >= 201402L) || (defined(_HAS_CXX14) && _HAS_CXX14 == 1) - #define JSON_HAS_CPP_14 -#endif - -// disable float-equal warnings on GCC/clang -#if defined(__clang__) || defined(__GNUC__) || defined(__GNUG__) - #pragma GCC diagnostic push - #pragma GCC diagnostic ignored "-Wfloat-equal" +// if the user manually specified the used c++ version this is skipped +#if !defined(JSON_HAS_CPP_20) && !defined(JSON_HAS_CPP_17) && !defined(JSON_HAS_CPP_14) && !defined(JSON_HAS_CPP_11) + #if (defined(__cplusplus) && __cplusplus >= 202002L) || (defined(_MSVC_LANG) && _MSVC_LANG >= 202002L) + #define JSON_HAS_CPP_20 + #define JSON_HAS_CPP_17 + #define JSON_HAS_CPP_14 + #elif (defined(__cplusplus) && __cplusplus >= 201703L) || (defined(_HAS_CXX17) && _HAS_CXX17 == 1) // fix for issue #464 + #define JSON_HAS_CPP_17 + #define JSON_HAS_CPP_14 + #elif (defined(__cplusplus) && __cplusplus >= 201402L) || (defined(_HAS_CXX14) && _HAS_CXX14 == 1) + #define JSON_HAS_CPP_14 + #endif + // the cpp 11 flag is always specified because it is the minimal required version + #define JSON_HAS_CPP_11 #endif // disable documentation warnings on clang @@ -2101,6 +2286,19 @@ JSON_HEDLEY_DIAGNOSTIC_POP #define JSON_INTERNAL_CATCH JSON_INTERNAL_CATCH_USER #endif +// allow to override assert +#if !defined(JSON_ASSERT) + #include // assert + #define JSON_ASSERT(x) assert(x) +#endif + +// allow to access some private functions (needed by the test suite) +#if defined(JSON_TESTS_PRIVATE) + #define JSON_PRIVATE_UNLESS_TESTED public +#else + #define JSON_PRIVATE_UNLESS_TESTED private +#endif + /*! @brief macro to briefly define a mapping between an enum and JSON @def NLOHMANN_JSON_SERIALIZE_ENUM @@ -2149,6 +2347,261 @@ JSON_HEDLEY_DIAGNOSTIC_POP NumberIntegerType, NumberUnsignedType, NumberFloatType, \ AllocatorType, JSONSerializer, BinaryType> +// Macros to simplify conversion from/to types + +#define NLOHMANN_JSON_EXPAND( x ) x +#define NLOHMANN_JSON_GET_MACRO(_1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, _13, _14, _15, _16, _17, _18, _19, _20, _21, _22, _23, _24, _25, _26, _27, _28, _29, _30, _31, _32, _33, _34, _35, _36, _37, _38, _39, _40, _41, _42, _43, _44, _45, _46, _47, _48, _49, _50, _51, _52, _53, _54, _55, _56, _57, _58, _59, _60, _61, _62, _63, _64, NAME,...) NAME +#define NLOHMANN_JSON_PASTE(...) NLOHMANN_JSON_EXPAND(NLOHMANN_JSON_GET_MACRO(__VA_ARGS__, \ + NLOHMANN_JSON_PASTE64, \ + NLOHMANN_JSON_PASTE63, \ + NLOHMANN_JSON_PASTE62, \ + NLOHMANN_JSON_PASTE61, \ + NLOHMANN_JSON_PASTE60, \ + NLOHMANN_JSON_PASTE59, \ + NLOHMANN_JSON_PASTE58, \ + NLOHMANN_JSON_PASTE57, \ + NLOHMANN_JSON_PASTE56, \ + NLOHMANN_JSON_PASTE55, \ + NLOHMANN_JSON_PASTE54, \ + NLOHMANN_JSON_PASTE53, \ + NLOHMANN_JSON_PASTE52, \ + NLOHMANN_JSON_PASTE51, \ + NLOHMANN_JSON_PASTE50, \ + NLOHMANN_JSON_PASTE49, \ + NLOHMANN_JSON_PASTE48, \ + NLOHMANN_JSON_PASTE47, \ + NLOHMANN_JSON_PASTE46, \ + NLOHMANN_JSON_PASTE45, \ + NLOHMANN_JSON_PASTE44, \ + NLOHMANN_JSON_PASTE43, \ + NLOHMANN_JSON_PASTE42, \ + NLOHMANN_JSON_PASTE41, \ + NLOHMANN_JSON_PASTE40, \ + NLOHMANN_JSON_PASTE39, \ + NLOHMANN_JSON_PASTE38, \ + NLOHMANN_JSON_PASTE37, \ + NLOHMANN_JSON_PASTE36, \ + NLOHMANN_JSON_PASTE35, \ + NLOHMANN_JSON_PASTE34, \ + NLOHMANN_JSON_PASTE33, \ + NLOHMANN_JSON_PASTE32, \ + NLOHMANN_JSON_PASTE31, \ + NLOHMANN_JSON_PASTE30, \ + NLOHMANN_JSON_PASTE29, \ + NLOHMANN_JSON_PASTE28, \ + NLOHMANN_JSON_PASTE27, \ + NLOHMANN_JSON_PASTE26, \ + NLOHMANN_JSON_PASTE25, \ + NLOHMANN_JSON_PASTE24, \ + NLOHMANN_JSON_PASTE23, \ + NLOHMANN_JSON_PASTE22, \ + NLOHMANN_JSON_PASTE21, \ + NLOHMANN_JSON_PASTE20, \ + NLOHMANN_JSON_PASTE19, \ + NLOHMANN_JSON_PASTE18, \ + NLOHMANN_JSON_PASTE17, \ + NLOHMANN_JSON_PASTE16, \ + NLOHMANN_JSON_PASTE15, \ + NLOHMANN_JSON_PASTE14, \ + NLOHMANN_JSON_PASTE13, \ + NLOHMANN_JSON_PASTE12, \ + NLOHMANN_JSON_PASTE11, \ + NLOHMANN_JSON_PASTE10, \ + NLOHMANN_JSON_PASTE9, \ + NLOHMANN_JSON_PASTE8, \ + NLOHMANN_JSON_PASTE7, \ + NLOHMANN_JSON_PASTE6, \ + NLOHMANN_JSON_PASTE5, \ + NLOHMANN_JSON_PASTE4, \ + NLOHMANN_JSON_PASTE3, \ + NLOHMANN_JSON_PASTE2, \ + NLOHMANN_JSON_PASTE1)(__VA_ARGS__)) +#define NLOHMANN_JSON_PASTE2(func, v1) func(v1) +#define NLOHMANN_JSON_PASTE3(func, v1, v2) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE2(func, v2) +#define NLOHMANN_JSON_PASTE4(func, v1, v2, v3) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE3(func, v2, v3) +#define NLOHMANN_JSON_PASTE5(func, v1, v2, v3, v4) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE4(func, v2, v3, v4) +#define NLOHMANN_JSON_PASTE6(func, v1, v2, v3, v4, v5) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE5(func, v2, v3, v4, v5) +#define NLOHMANN_JSON_PASTE7(func, v1, v2, v3, v4, v5, v6) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE6(func, v2, v3, v4, v5, v6) +#define NLOHMANN_JSON_PASTE8(func, v1, v2, v3, v4, v5, v6, v7) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE7(func, v2, v3, v4, v5, v6, v7) +#define NLOHMANN_JSON_PASTE9(func, v1, v2, v3, v4, v5, v6, v7, v8) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE8(func, v2, v3, v4, v5, v6, v7, v8) +#define NLOHMANN_JSON_PASTE10(func, v1, v2, v3, v4, v5, v6, v7, v8, v9) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE9(func, v2, v3, v4, v5, v6, v7, v8, v9) +#define NLOHMANN_JSON_PASTE11(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE10(func, v2, v3, v4, v5, v6, v7, v8, v9, v10) +#define NLOHMANN_JSON_PASTE12(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE11(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11) +#define NLOHMANN_JSON_PASTE13(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE12(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12) +#define NLOHMANN_JSON_PASTE14(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE13(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13) +#define NLOHMANN_JSON_PASTE15(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE14(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14) +#define NLOHMANN_JSON_PASTE16(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE15(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15) +#define NLOHMANN_JSON_PASTE17(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE16(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16) +#define NLOHMANN_JSON_PASTE18(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE17(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17) +#define NLOHMANN_JSON_PASTE19(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE18(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18) +#define NLOHMANN_JSON_PASTE20(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE19(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19) +#define NLOHMANN_JSON_PASTE21(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE20(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20) +#define NLOHMANN_JSON_PASTE22(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE21(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21) +#define NLOHMANN_JSON_PASTE23(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE22(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22) +#define NLOHMANN_JSON_PASTE24(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE23(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23) +#define NLOHMANN_JSON_PASTE25(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE24(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24) +#define NLOHMANN_JSON_PASTE26(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE25(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25) +#define NLOHMANN_JSON_PASTE27(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE26(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26) +#define NLOHMANN_JSON_PASTE28(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE27(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27) +#define NLOHMANN_JSON_PASTE29(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE28(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28) +#define NLOHMANN_JSON_PASTE30(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE29(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29) +#define NLOHMANN_JSON_PASTE31(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE30(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30) +#define NLOHMANN_JSON_PASTE32(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE31(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31) +#define NLOHMANN_JSON_PASTE33(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE32(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32) +#define NLOHMANN_JSON_PASTE34(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE33(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33) +#define NLOHMANN_JSON_PASTE35(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE34(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34) +#define NLOHMANN_JSON_PASTE36(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE35(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35) +#define NLOHMANN_JSON_PASTE37(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE36(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36) +#define NLOHMANN_JSON_PASTE38(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE37(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37) +#define NLOHMANN_JSON_PASTE39(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE38(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38) +#define NLOHMANN_JSON_PASTE40(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE39(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39) +#define NLOHMANN_JSON_PASTE41(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE40(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40) +#define NLOHMANN_JSON_PASTE42(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE41(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41) +#define NLOHMANN_JSON_PASTE43(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE42(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42) +#define NLOHMANN_JSON_PASTE44(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42, v43) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE43(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42, v43) +#define NLOHMANN_JSON_PASTE45(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42, v43, v44) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE44(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42, v43, v44) +#define NLOHMANN_JSON_PASTE46(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42, v43, v44, v45) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE45(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42, v43, v44, v45) +#define NLOHMANN_JSON_PASTE47(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42, v43, v44, v45, v46) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE46(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42, v43, v44, v45, v46) +#define NLOHMANN_JSON_PASTE48(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42, v43, v44, v45, v46, v47) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE47(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42, v43, v44, v45, v46, v47) +#define NLOHMANN_JSON_PASTE49(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42, v43, v44, v45, v46, v47, v48) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE48(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42, v43, v44, v45, v46, v47, v48) +#define NLOHMANN_JSON_PASTE50(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42, v43, v44, v45, v46, v47, v48, v49) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE49(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42, v43, v44, v45, v46, v47, v48, v49) +#define NLOHMANN_JSON_PASTE51(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42, v43, v44, v45, v46, v47, v48, v49, v50) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE50(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42, v43, v44, v45, v46, v47, v48, v49, v50) +#define NLOHMANN_JSON_PASTE52(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42, v43, v44, v45, v46, v47, v48, v49, v50, v51) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE51(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42, v43, v44, v45, v46, v47, v48, v49, v50, v51) +#define NLOHMANN_JSON_PASTE53(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42, v43, v44, v45, v46, v47, v48, v49, v50, v51, v52) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE52(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42, v43, v44, v45, v46, v47, v48, v49, v50, v51, v52) +#define NLOHMANN_JSON_PASTE54(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42, v43, v44, v45, v46, v47, v48, v49, v50, v51, v52, v53) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE53(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42, v43, v44, v45, v46, v47, v48, v49, v50, v51, v52, v53) +#define NLOHMANN_JSON_PASTE55(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42, v43, v44, v45, v46, v47, v48, v49, v50, v51, v52, v53, v54) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE54(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42, v43, v44, v45, v46, v47, v48, v49, v50, v51, v52, v53, v54) +#define NLOHMANN_JSON_PASTE56(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42, v43, v44, v45, v46, v47, v48, v49, v50, v51, v52, v53, v54, v55) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE55(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42, v43, v44, v45, v46, v47, v48, v49, v50, v51, v52, v53, v54, v55) +#define NLOHMANN_JSON_PASTE57(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42, v43, v44, v45, v46, v47, v48, v49, v50, v51, v52, v53, v54, v55, v56) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE56(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42, v43, v44, v45, v46, v47, v48, v49, v50, v51, v52, v53, v54, v55, v56) +#define NLOHMANN_JSON_PASTE58(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42, v43, v44, v45, v46, v47, v48, v49, v50, v51, v52, v53, v54, v55, v56, v57) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE57(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42, v43, v44, v45, v46, v47, v48, v49, v50, v51, v52, v53, v54, v55, v56, v57) +#define NLOHMANN_JSON_PASTE59(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42, v43, v44, v45, v46, v47, v48, v49, v50, v51, v52, v53, v54, v55, v56, v57, v58) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE58(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42, v43, v44, v45, v46, v47, v48, v49, v50, v51, v52, v53, v54, v55, v56, v57, v58) +#define NLOHMANN_JSON_PASTE60(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42, v43, v44, v45, v46, v47, v48, v49, v50, v51, v52, v53, v54, v55, v56, v57, v58, v59) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE59(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42, v43, v44, v45, v46, v47, v48, v49, v50, v51, v52, v53, v54, v55, v56, v57, v58, v59) +#define NLOHMANN_JSON_PASTE61(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42, v43, v44, v45, v46, v47, v48, v49, v50, v51, v52, v53, v54, v55, v56, v57, v58, v59, v60) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE60(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42, v43, v44, v45, v46, v47, v48, v49, v50, v51, v52, v53, v54, v55, v56, v57, v58, v59, v60) +#define NLOHMANN_JSON_PASTE62(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42, v43, v44, v45, v46, v47, v48, v49, v50, v51, v52, v53, v54, v55, v56, v57, v58, v59, v60, v61) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE61(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42, v43, v44, v45, v46, v47, v48, v49, v50, v51, v52, v53, v54, v55, v56, v57, v58, v59, v60, v61) +#define NLOHMANN_JSON_PASTE63(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42, v43, v44, v45, v46, v47, v48, v49, v50, v51, v52, v53, v54, v55, v56, v57, v58, v59, v60, v61, v62) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE62(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42, v43, v44, v45, v46, v47, v48, v49, v50, v51, v52, v53, v54, v55, v56, v57, v58, v59, v60, v61, v62) +#define NLOHMANN_JSON_PASTE64(func, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42, v43, v44, v45, v46, v47, v48, v49, v50, v51, v52, v53, v54, v55, v56, v57, v58, v59, v60, v61, v62, v63) NLOHMANN_JSON_PASTE2(func, v1) NLOHMANN_JSON_PASTE63(func, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, v42, v43, v44, v45, v46, v47, v48, v49, v50, v51, v52, v53, v54, v55, v56, v57, v58, v59, v60, v61, v62, v63) + +#define NLOHMANN_JSON_TO(v1) nlohmann_json_j[#v1] = nlohmann_json_t.v1; +#define NLOHMANN_JSON_FROM(v1) nlohmann_json_j.at(#v1).get_to(nlohmann_json_t.v1); + +/*! +@brief macro +@def NLOHMANN_DEFINE_TYPE_INTRUSIVE +@since version 3.9.0 +*/ +#define NLOHMANN_DEFINE_TYPE_INTRUSIVE(Type, ...) \ + friend void to_json(nlohmann::json& nlohmann_json_j, const Type& nlohmann_json_t) { NLOHMANN_JSON_EXPAND(NLOHMANN_JSON_PASTE(NLOHMANN_JSON_TO, __VA_ARGS__)) } \ + friend void from_json(const nlohmann::json& nlohmann_json_j, Type& nlohmann_json_t) { NLOHMANN_JSON_EXPAND(NLOHMANN_JSON_PASTE(NLOHMANN_JSON_FROM, __VA_ARGS__)) } + +/*! +@brief macro +@def NLOHMANN_DEFINE_TYPE_NON_INTRUSIVE +@since version 3.9.0 +*/ +#define NLOHMANN_DEFINE_TYPE_NON_INTRUSIVE(Type, ...) \ + inline void to_json(nlohmann::json& nlohmann_json_j, const Type& nlohmann_json_t) { NLOHMANN_JSON_EXPAND(NLOHMANN_JSON_PASTE(NLOHMANN_JSON_TO, __VA_ARGS__)) } \ + inline void from_json(const nlohmann::json& nlohmann_json_j, Type& nlohmann_json_t) { NLOHMANN_JSON_EXPAND(NLOHMANN_JSON_PASTE(NLOHMANN_JSON_FROM, __VA_ARGS__)) } + +#ifndef JSON_USE_IMPLICIT_CONVERSIONS + #define JSON_USE_IMPLICIT_CONVERSIONS 1 +#endif + +#if JSON_USE_IMPLICIT_CONVERSIONS + #define JSON_EXPLICIT +#else + #define JSON_EXPLICIT explicit +#endif + + +namespace nlohmann +{ +namespace detail +{ + +/*! +@brief replace all occurrences of a substring by another string + +@param[in,out] s the string to manipulate; changed so that all + occurrences of @a f are replaced with @a t +@param[in] f the substring to replace with @a t +@param[in] t the string to replace @a f + +@pre The search string @a f must not be empty. **This precondition is +enforced with an assertion.** + +@since version 2.0.0 +*/ +inline void replace_substring(std::string& s, const std::string& f, + const std::string& t) +{ + JSON_ASSERT(!f.empty()); + for (auto pos = s.find(f); // find first occurrence of f + pos != std::string::npos; // make sure f was found + s.replace(pos, f.size(), t), // replace with t, and + pos = s.find(f, pos + t.size())) // find next occurrence of f + {} +} + +/*! + * @brief string escaping as described in RFC 6901 (Sect. 4) + * @param[in] s string to escape + * @return escaped string + * + * Note the order of escaping "~" to "~0" and "/" to "~1" is important. + */ +inline std::string escape(std::string s) +{ + replace_substring(s, "~", "~0"); + replace_substring(s, "/", "~1"); + return s; +} + +/*! + * @brief string unescaping as described in RFC 6901 (Sect. 4) + * @param[in] s string to unescape + * @return unescaped string + * + * Note the order of escaping "~1" to "/" and "~0" to "~" is important. + */ +static void unescape(std::string& s) +{ + replace_substring(s, "~1", "/"); + replace_substring(s, "~0", "~"); +} + +} // namespace detail +} // namespace nlohmann + +// #include + + +#include // size_t + +namespace nlohmann +{ +namespace detail +{ +/// struct to capture the start position of the current token +struct position_t +{ + /// the total number of characters read + std::size_t chars_read_total = 0; + /// the number of characters read in the current line + std::size_t chars_read_current_line = 0; + /// the number of lines read + std::size_t lines_read = 0; + + /// conversion to size_t to preserve SAX interface + constexpr operator size_t() const + { + return chars_read_total; + } +}; + +} // namespace detail +} // namespace nlohmann + +// #include + namespace nlohmann { @@ -2190,14 +2643,13 @@ class exception : public std::exception { public: /// returns the explanatory string - JSON_HEDLEY_RETURNS_NON_NULL const char* what() const noexcept override { return m.what(); } /// the id of the exception - const int id; + const int id; // NOLINT(cppcoreguidelines-non-private-member-variables-in-classes) protected: JSON_HEDLEY_NON_NULL(3) @@ -2208,6 +2660,62 @@ class exception : public std::exception return "[json.exception." + ename + "." + std::to_string(id_) + "] "; } + template + static std::string diagnostics(const BasicJsonType& leaf_element) + { +#if JSON_DIAGNOSTICS + std::vector tokens; + for (const auto* current = &leaf_element; current->m_parent != nullptr; current = current->m_parent) + { + switch (current->m_parent->type()) + { + case value_t::array: + { + for (std::size_t i = 0; i < current->m_parent->m_value.array->size(); ++i) + { + if (¤t->m_parent->m_value.array->operator[](i) == current) + { + tokens.emplace_back(std::to_string(i)); + break; + } + } + break; + } + + case value_t::object: + { + for (const auto& element : *current->m_parent->m_value.object) + { + if (&element.second == current) + { + tokens.emplace_back(element.first.c_str()); + break; + } + } + break; + } + + default: // LCOV_EXCL_LINE + break; // LCOV_EXCL_LINE + } + } + + if (tokens.empty()) + { + return ""; + } + + return "(" + std::accumulate(tokens.rbegin(), tokens.rend(), std::string{}, + [](const std::string & a, const std::string & b) + { + return a + "/" + detail::escape(b); + }) + ") "; +#else + static_cast(leaf_element); + return ""; +#endif + } + private: /// an exception object as storage for error messages std::runtime_error m; @@ -2240,6 +2748,7 @@ json.exception.parse_error.110 | parse error at 1: cannot read 2 bytes from vect json.exception.parse_error.112 | parse error at 1: error reading CBOR; last byte: 0xF8 | Not all types of CBOR or MessagePack are supported. This exception occurs if an unsupported byte was read. json.exception.parse_error.113 | parse error at 2: expected a CBOR string; last byte: 0x98 | While parsing a map key, a value that is not a string has been read. json.exception.parse_error.114 | parse error: Unsupported BSON record type 0x0F | The parsing of the corresponding BSON record type is not implemented (yet). +json.exception.parse_error.115 | parse error at byte 5: syntax error while parsing UBJSON high-precision number: invalid number text: 1A | A UBJSON high-precision number could not be parsed. @note For an input with n bytes, 1 is the index of the first character and n+1 is the index of the terminating null byte or the end of file. This also @@ -2269,18 +2778,20 @@ class parse_error : public exception @param[in] what_arg the explanatory string @return parse_error object */ - static parse_error create(int id_, const position_t& pos, const std::string& what_arg) + template + static parse_error create(int id_, const position_t& pos, const std::string& what_arg, const BasicJsonType& context) { std::string w = exception::name("parse_error", id_) + "parse error" + - position_string(pos) + ": " + what_arg; + position_string(pos) + ": " + exception::diagnostics(context) + what_arg; return parse_error(id_, pos.chars_read_total, w.c_str()); } - static parse_error create(int id_, std::size_t byte_, const std::string& what_arg) + template + static parse_error create(int id_, std::size_t byte_, const std::string& what_arg, const BasicJsonType& context) { std::string w = exception::name("parse_error", id_) + "parse error" + (byte_ != 0 ? (" at byte " + std::to_string(byte_)) : "") + - ": " + what_arg; + ": " + exception::diagnostics(context) + what_arg; return parse_error(id_, byte_, w.c_str()); } @@ -2346,9 +2857,10 @@ caught.,invalid_iterator} class invalid_iterator : public exception { public: - static invalid_iterator create(int id_, const std::string& what_arg) + template + static invalid_iterator create(int id_, const std::string& what_arg, const BasicJsonType& context) { - std::string w = exception::name("invalid_iterator", id_) + what_arg; + std::string w = exception::name("invalid_iterator", id_) + exception::diagnostics(context) + what_arg; return invalid_iterator(id_, w.c_str()); } @@ -2400,9 +2912,10 @@ caught.,type_error} class type_error : public exception { public: - static type_error create(int id_, const std::string& what_arg) + template + static type_error create(int id_, const std::string& what_arg, const BasicJsonType& context) { - std::string w = exception::name("type_error", id_) + what_arg; + std::string w = exception::name("type_error", id_) + exception::diagnostics(context) + what_arg; return type_error(id_, w.c_str()); } @@ -2428,7 +2941,7 @@ json.exception.out_of_range.403 | key 'foo' not found | The provided key was not json.exception.out_of_range.404 | unresolved reference token 'foo' | A reference token in a JSON Pointer could not be resolved. json.exception.out_of_range.405 | JSON pointer has no parent | The JSON Patch operations 'remove' and 'add' can not be applied to the root element of the JSON value. json.exception.out_of_range.406 | number overflow parsing '10E1000' | A parsed number could not be stored as without changing it to NaN or INF. -json.exception.out_of_range.407 | number overflow serializing '9223372036854775808' | UBJSON and BSON only support integer numbers up to 9223372036854775807. | +json.exception.out_of_range.407 | number overflow serializing '9223372036854775808' | UBJSON and BSON only support integer numbers up to 9223372036854775807. (until version 3.8.0) | json.exception.out_of_range.408 | excessive array size: 8658170730974374167 | The size (following `#`) of an UBJSON array or object exceeds the maximal capacity. | json.exception.out_of_range.409 | BSON key cannot contain code point U+0000 (at byte 2) | Key identifiers to be serialized to BSON cannot contain code point U+0000, since the key is stored as zero-terminated c-string | @@ -2447,9 +2960,10 @@ caught.,out_of_range} class out_of_range : public exception { public: - static out_of_range create(int id_, const std::string& what_arg) + template + static out_of_range create(int id_, const std::string& what_arg, const BasicJsonType& context) { - std::string w = exception::name("out_of_range", id_) + what_arg; + std::string w = exception::name("out_of_range", id_) + exception::diagnostics(context) + what_arg; return out_of_range(id_, w.c_str()); } @@ -2485,9 +2999,10 @@ caught.,other_error} class other_error : public exception { public: - static other_error create(int id_, const std::string& what_arg) + template + static other_error create(int id_, const std::string& what_arg, const BasicJsonType& context) { - std::string w = exception::name("other_error", id_) + what_arg; + std::string w = exception::name("other_error", id_) + exception::diagnostics(context) + what_arg; return other_error(id_, w.c_str()); } @@ -2505,52 +3020,141 @@ class other_error : public exception #include // size_t #include // conditional, enable_if, false_type, integral_constant, is_constructible, is_integral, is_same, remove_cv, remove_reference, true_type +#include // index_sequence, make_index_sequence, index_sequence_for -// #include +// #include namespace nlohmann { namespace detail { + +template +using uncvref_t = typename std::remove_cv::type>::type; + +#ifdef JSON_HAS_CPP_14 + +// the following utilities are natively available in C++14 +using std::enable_if_t; +using std::index_sequence; +using std::make_index_sequence; +using std::index_sequence_for; + +#else + // alias templates to reduce boilerplate template using enable_if_t = typename std::enable_if::type; -template -using uncvref_t = typename std::remove_cv::type>::type; +// The following code is taken from https://github.com/abseil/abseil-cpp/blob/10cb35e459f5ecca5b2ff107635da0bfa41011b4/absl/utility/utility.h +// which is part of Google Abseil (https://github.com/abseil/abseil-cpp), licensed under the Apache License 2.0. -// implementation of C++14 index_sequence and affiliates -// source: https://stackoverflow.com/a/32223343 -template -struct index_sequence +//// START OF CODE FROM GOOGLE ABSEIL + +// integer_sequence +// +// Class template representing a compile-time integer sequence. An instantiation +// of `integer_sequence` has a sequence of integers encoded in its +// type through its template arguments (which is a common need when +// working with C++11 variadic templates). `absl::integer_sequence` is designed +// to be a drop-in replacement for C++14's `std::integer_sequence`. +// +// Example: +// +// template< class T, T... Ints > +// void user_function(integer_sequence); +// +// int main() +// { +// // user_function's `T` will be deduced to `int` and `Ints...` +// // will be deduced to `0, 1, 2, 3, 4`. +// user_function(make_integer_sequence()); +// } +template +struct integer_sequence { - using type = index_sequence; - using value_type = std::size_t; + using value_type = T; static constexpr std::size_t size() noexcept { return sizeof...(Ints); } }; -template -struct merge_and_renumber; +// index_sequence +// +// A helper template for an `integer_sequence` of `size_t`, +// `absl::index_sequence` is designed to be a drop-in replacement for C++14's +// `std::index_sequence`. +template +using index_sequence = integer_sequence; + +namespace utility_internal +{ + +template +struct Extend; + +// Note that SeqSize == sizeof...(Ints). It's passed explicitly for efficiency. +template +struct Extend, SeqSize, 0> +{ + using type = integer_sequence < T, Ints..., (Ints + SeqSize)... >; +}; + +template +struct Extend, SeqSize, 1> +{ + using type = integer_sequence < T, Ints..., (Ints + SeqSize)..., 2 * SeqSize >; +}; + +// Recursion helper for 'make_integer_sequence'. +// 'Gen::type' is an alias for 'integer_sequence'. +template +struct Gen +{ + using type = + typename Extend < typename Gen < T, N / 2 >::type, N / 2, N % 2 >::type; +}; + +template +struct Gen +{ + using type = integer_sequence; +}; + +} // namespace utility_internal -template -struct merge_and_renumber, index_sequence> - : index_sequence < I1..., (sizeof...(I1) + I2)... > {}; +// Compile-time sequences of integers -template -struct make_index_sequence - : merge_and_renumber < typename make_index_sequence < N / 2 >::type, - typename make_index_sequence < N - N / 2 >::type > {}; +// make_integer_sequence +// +// This template alias is equivalent to +// `integer_sequence`, and is designed to be a drop-in +// replacement for C++14's `std::make_integer_sequence`. +template +using make_integer_sequence = typename utility_internal::Gen::type; -template<> struct make_index_sequence<0> : index_sequence<> {}; -template<> struct make_index_sequence<1> : index_sequence<0> {}; +// make_index_sequence +// +// This template alias is equivalent to `index_sequence<0, 1, ..., N-1>`, +// and is designed to be a drop-in replacement for C++14's +// `std::make_index_sequence`. +template +using make_index_sequence = make_integer_sequence; -template +// index_sequence_for +// +// Converts a typename pack into an index sequence of the same length, and +// is designed to be a drop-in replacement for C++14's +// `std::index_sequence_for()` +template using index_sequence_for = make_index_sequence; +//// END OF CODE FROM GOOGLE ABSEIL + +#endif + // dispatch utility (taken from ranges-v3) template struct priority_tag : priority_tag < N - 1 > {}; template<> struct priority_tag<0> {}; @@ -2564,6 +3168,19 @@ struct static_const template constexpr T static_const::value; + +} // namespace detail +} // namespace nlohmann + +// #include + + +namespace nlohmann +{ +namespace detail +{ +// dispatching helper struct +template struct identity_tag {}; } // namespace detail } // namespace nlohmann @@ -2573,8 +3190,7 @@ constexpr T static_const::value; #include // numeric_limits #include // false_type, is_constructible, is_integral, is_same, true_type #include // declval - -// #include +#include // tuple // #include @@ -2588,11 +3204,11 @@ namespace nlohmann { namespace detail { -template struct make_void +template struct make_void { using type = void; }; -template using void_t = typename make_void::type; +template using void_t = typename make_void::type; } // namespace detail } // namespace nlohmann @@ -2603,10 +3219,10 @@ namespace nlohmann { namespace detail { -template +template struct iterator_types {}; -template +template struct iterator_types < It, void_t +template struct iterator_traits { }; -template +template struct iterator_traits < T, enable_if_t < !std::is_pointer::value >> : iterator_types { }; -template +template struct iterator_traits::value>> { using iterator_category = std::random_access_iterator_tag; @@ -2671,39 +3287,39 @@ struct nonesuch void operator=(nonesuch&&) = delete; }; -template class Op, - class... Args> +template class Op, + class... Args> struct detector { using value_t = std::false_type; using type = Default; }; -template class Op, class... Args> +template class Op, class... Args> struct detector>, Op, Args...> { using value_t = std::true_type; using type = Op; }; -template