From 6092f44df187655e203206acc9fa8b76f2e0e9fc Mon Sep 17 00:00:00 2001 From: rmorozov Date: Sun, 12 Dec 2021 23:20:15 +0300 Subject: [PATCH 01/14] Bump deps: Switch to polymorphic value and latest robin_hood_hash(#220) * bump robin_hood hashing dep to 3.11.3 * bump value-ptr-lite to latest version * switch to polymorphic_value * implement comparisons * fix build: add operator!= * polymorphic_value into jinja2cpp integration * introduce ValuePtr and MoveValuePtr aliases --- .github/workflows/windows-build.yml | 2 +- .gitignore | 1 + CMakeLists.txt | 10 +- include/jinja2cpp/binding/nlohmann_json.h | 27 +- include/jinja2cpp/binding/rapid_json.h | 44 +- include/jinja2cpp/filesystem_handler.h | 27 +- include/jinja2cpp/generic_list.h | 48 +- include/jinja2cpp/generic_list_impl.h | 125 +- include/jinja2cpp/generic_list_iterator.h | 77 +- include/jinja2cpp/polymorphic_value.h | 447 ++++++ include/jinja2cpp/reflected_value.h | 89 +- include/jinja2cpp/template.h | 21 + include/jinja2cpp/template_env.h | 73 + include/jinja2cpp/user_callable.h | 66 +- include/jinja2cpp/utils/i_comparable.h | 16 + include/jinja2cpp/value.h | 156 ++- include/jinja2cpp/value_ptr.h | 29 + include/jinja2cpp/value_ptr.hpp | 1295 ------------------ src/ast_visitor.h | 8 +- src/error_info.cpp | 2 +- src/expression_evaluator.h | 265 +++- src/filesystem_handler.cpp | 16 + src/filters.h | 237 +++- src/function_base.h | 13 + src/generic_adapters.h | 45 +- src/generic_list.cpp | 38 + src/internal_value.cpp | 241 +++- src/internal_value.h | 36 +- src/render_context.h | 64 +- src/renderer.h | 60 +- src/robin_hood.h | 1496 ++++++++++++++------- src/statements.cpp | 60 +- src/statements.h | 248 +++- src/template.cpp | 19 + src/template_impl.h | 91 +- src/testers.cpp | 6 +- src/testers.h | 29 + src/value_visitors.h | 92 +- 38 files changed, 3505 insertions(+), 2114 deletions(-) create mode 100644 include/jinja2cpp/polymorphic_value.h create mode 100644 include/jinja2cpp/utils/i_comparable.h create mode 100644 include/jinja2cpp/value_ptr.h delete mode 100644 include/jinja2cpp/value_ptr.hpp create mode 100644 src/generic_list.cpp diff --git a/.github/workflows/windows-build.yml b/.github/workflows/windows-build.yml index c2d9af08..dedd5179 100644 --- a/.github/workflows/windows-build.yml +++ b/.github/workflows/windows-build.yml @@ -75,7 +75,7 @@ jobs: mkdir -p .build cd .build cmake .. -G "%INPUT_GENERATOR%" -DCMAKE_BUILD_TYPE=%INPUT_BUILD_CONFIG% -DJINJA2CPP_MSVC_RUNTIME_TYPE="%INPUT_BUILD_RUNTIME%" -DJINJA2CPP_DEPS_MODE=internal -DJINJA2CPP_BUILD_SHARED=%INPUT_BUILD_SHARED% %INPUT_BASE_FLAGS% %INPUT_EXTRA_FLAGS% - cmake --build . --config %INPUT_BUILD_CONFIG% + cmake --build . --config %INPUT_BUILD_CONFIG% --verbose - name: Test shell: cmd diff --git a/.gitignore b/.gitignore index 091eea38..8d1483f7 100644 --- a/.gitignore +++ b/.gitignore @@ -33,3 +33,4 @@ build/ dist/ +compile_commands.json diff --git a/CMakeLists.txt b/CMakeLists.txt index bf603b0a..4bd7fd8a 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -90,7 +90,7 @@ if (UNIX) endif () else () set(GCC_CXX_FLAGS ${GCC_CXX_FLAGS} "-Wa,-mbig-obj" -O1) - if (${CMAKE_CXX_COMPILER_ID} MATCHES "GNU") + if (CMAKE_CXX_COMPILER_ID MATCHES "GNU") add_definitions(-D_CRT_SECURE_NO_WARNINGS) endif () add_definitions(-DBOOST_ALL_NO_LIB) @@ -193,11 +193,11 @@ if (JINJA2CPP_STRICT_WARNINGS) endif () endif () -if (${CMAKE_CXX_COMPILER_ID} MATCHES "GNU") +if (CMAKE_CXX_COMPILER_ID MATCHES "GNU") target_compile_options(${LIB_TARGET_NAME} PRIVATE ${GCC_CXX_FLAGS}) -elseif (${CMAKE_CXX_COMPILER_ID} MATCHES "Clang") +elseif (CMAKE_CXX_COMPILER_ID MATCHES "Clang") target_compile_options(${LIB_TARGET_NAME} PRIVATE ${CLANG_CXX_FLAGS}) -elseif (${CMAKE_CXX_COMPILER_ID} MATCHES "MSVC") +elseif (CMAKE_CXX_COMPILER_ID MATCHES "MSVC") target_compile_options(${LIB_TARGET_NAME} PRIVATE ${MSVC_CXX_FLAGS}) endif () @@ -233,7 +233,7 @@ if (JINJA2CPP_BUILD_TESTS) CXX_STANDARD ${JINJA2CPP_CXX_STANDARD} CXX_STANDARD_REQUIRED ON) - if (MSVC) + if (CMAKE_CXX_COMPILER_ID MATCHES "MSVC") target_compile_options(jinja2cpp_tests PRIVATE /bigobj) endif () diff --git a/include/jinja2cpp/binding/nlohmann_json.h b/include/jinja2cpp/binding/nlohmann_json.h index 2a7cbfdd..b6a302a4 100644 --- a/include/jinja2cpp/binding/nlohmann_json.h +++ b/include/jinja2cpp/binding/nlohmann_json.h @@ -10,7 +10,7 @@ namespace jinja2 namespace detail { -class NLohmannJsonObjectAccessor : public MapItemAccessor, public ReflectedDataHolder +class NLohmannJsonObjectAccessor : public IMapItemAccessor, public ReflectedDataHolder { public: using ReflectedDataHolder::ReflectedDataHolder; @@ -50,10 +50,18 @@ class NLohmannJsonObjectAccessor : public MapItemAccessor, public ReflectedDataH } return result; } + + bool IsEqual(const IComparable& other) const override + { + auto* val = dynamic_cast(&other); + if (!val) + return false; + return GetValue() == val->GetValue(); + } }; -struct NLohmannJsonArrayAccessor : ListItemAccessor, IndexBasedAccessor, ReflectedDataHolder +struct NLohmannJsonArrayAccessor : IListItemAccessor, IIndexBasedAccessor, ReflectedDataHolder { using ReflectedDataHolder::ReflectedDataHolder; @@ -62,7 +70,8 @@ struct NLohmannJsonArrayAccessor : ListItemAccessor, IndexBasedAccessor, Reflect auto j = this->GetValue(); return j ? j->size() : nonstd::optional(); } - const IndexBasedAccessor* GetIndexer() const override + + const IIndexBasedAccessor* GetIndexer() const override { return this; } @@ -72,9 +81,9 @@ struct NLohmannJsonArrayAccessor : ListItemAccessor, IndexBasedAccessor, Reflect using Enum = Enumerator; auto j = this->GetValue(); if (!j) - jinja2::ListEnumeratorPtr(nullptr, Enum::Deleter); + return jinja2::ListEnumeratorPtr(); - return jinja2::ListEnumeratorPtr(new Enum(j->begin(), j->end()), Enum::Deleter); + return jinja2::ListEnumeratorPtr(new Enum(j->begin(), j->end())); } Value GetItemByIndex(int64_t idx) const override @@ -85,6 +94,14 @@ struct NLohmannJsonArrayAccessor : ListItemAccessor, IndexBasedAccessor, Reflect return Reflect((*j)[idx]); } + + bool IsEqual(const IComparable& other) const override + { + auto* val = dynamic_cast(&other); + if (!val) + return false; + return GetValue() == val->GetValue(); + } }; template<> diff --git a/include/jinja2cpp/binding/rapid_json.h b/include/jinja2cpp/binding/rapid_json.h index 19702374..88531314 100644 --- a/include/jinja2cpp/binding/rapid_json.h +++ b/include/jinja2cpp/binding/rapid_json.h @@ -3,6 +3,7 @@ #include #include + #include #include @@ -27,11 +28,12 @@ struct RapidJsonNameConverter }; template -class RapidJsonObjectAccessor : public MapItemAccessor, public ReflectedDataHolder +class RapidJsonObjectAccessor : public IMapItemAccessor, public ReflectedDataHolder { public: using ReflectedDataHolder::ReflectedDataHolder; using NameCvt = RapidJsonNameConverter; + using ThisType = RapidJsonObjectAccessor; ~RapidJsonObjectAccessor() override = default; size_t GetSize() const override @@ -69,22 +71,38 @@ class RapidJsonObjectAccessor : public MapItemAccessor, public ReflectedDataHold } return result; } + + bool IsEqual(const IComparable& other) const override + { + auto* val = dynamic_cast(&other); + if (!val) + return false; + auto enumerator = this->GetValue(); + auto otherEnum = val->GetValue(); + if (enumerator && otherEnum && enumerator != otherEnum) + return false; + if ((enumerator && !otherEnum) || (!enumerator && otherEnum)) + return false; + return true; + } }; template struct RapidJsonArrayAccessor - : ListItemAccessor - , IndexBasedAccessor + : IListItemAccessor + , IIndexBasedAccessor , ReflectedDataHolder, false> { using ReflectedDataHolder, false>::ReflectedDataHolder; + using ThisType = RapidJsonArrayAccessor; nonstd::optional GetSize() const override { auto j = this->GetValue(); return j ? j->Size() : nonstd::optional(); } - const IndexBasedAccessor* GetIndexer() const override + + const IIndexBasedAccessor* GetIndexer() const override { return this; } @@ -94,9 +112,9 @@ struct RapidJsonArrayAccessor using Enum = Enumerator::ConstValueIterator>; auto j = this->GetValue(); if (!j) - jinja2::ListEnumeratorPtr(nullptr, Enum::Deleter); + return jinja2::ListEnumeratorPtr(); - return jinja2::ListEnumeratorPtr(new Enum(j->Begin(), j->End()), Enum::Deleter); + return jinja2::ListEnumeratorPtr(new Enum(j->Begin(), j->End())); } Value GetItemByIndex(int64_t idx) const override @@ -107,6 +125,20 @@ struct RapidJsonArrayAccessor return Reflect((*j)[static_cast(idx)]); } + + bool IsEqual(const IComparable& other) const override + { + auto* val = dynamic_cast(&other); + if (!val) + return false; + auto enumerator = this->GetValue(); + auto otherEnum = val->GetValue(); + if (enumerator && otherEnum && enumerator != otherEnum) + return false; + if ((enumerator && !otherEnum) || (!enumerator && otherEnum)) + return false; + return true; + } }; template diff --git a/include/jinja2cpp/filesystem_handler.h b/include/jinja2cpp/filesystem_handler.h index 868b498c..fedbfa5a 100644 --- a/include/jinja2cpp/filesystem_handler.h +++ b/include/jinja2cpp/filesystem_handler.h @@ -3,6 +3,8 @@ #include "config.h" +#include + #include #include @@ -28,7 +30,7 @@ using WCharFileStreamPtr = FileStreamPtr; * So, the exact type (ex. `ifstream`, `istringstream` etc.) of input stream is unspecified. In order to delete stream object correctly returned pointer * provide the custom deleter which should properly delete the stream object. */ -class JINJA2CPP_EXPORT IFilesystemHandler +class JINJA2CPP_EXPORT IFilesystemHandler : public IComparable { public: //! Destructor @@ -101,11 +103,27 @@ class JINJA2CPP_EXPORT MemoryFileSystem : public IFilesystemHandler WCharFileStreamPtr OpenWStream(const std::string& name) const override; nonstd::optional GetLastModificationDate(const std::string& name) const override; + /*! + * \brief Compares to an object of the same type + * + * return true if equal + */ + bool IsEqual(const IComparable& other) const override; private: struct FileContent { nonstd::optional narrowContent; nonstd::optional wideContent; + bool operator==(const FileContent& other) const + { + if (narrowContent != other.narrowContent) + return false; + return wideContent == other.wideContent; + } + bool operator!=(const FileContent& other) const + { + return !(*this == other); + } }; mutable std::unordered_map m_filesMap; }; @@ -168,6 +186,13 @@ class JINJA2CPP_EXPORT RealFileSystem : public IFilesystemHandler CharFileStreamPtr OpenByteStream(const std::string& name) const; nonstd::optional GetLastModificationDate(const std::string& name) const override; + /*! + * \brief Compares to an object of the same type + * + * return true if equal + */ + bool IsEqual(const IComparable& other) const override; + private: std::string m_rootFolder; }; diff --git a/include/jinja2cpp/generic_list.h b/include/jinja2cpp/generic_list.h index f95c6e2e..d18eb406 100644 --- a/include/jinja2cpp/generic_list.h +++ b/include/jinja2cpp/generic_list.h @@ -1,6 +1,9 @@ #ifndef JINJA2CPP_GENERIC_LIST_H #define JINJA2CPP_GENERIC_LIST_H +#include +#include + #include #include @@ -16,8 +19,9 @@ class Value; * * This interface should provided by the particular list implementation in case of support index-based access to the items. */ -struct IndexBasedAccessor +struct IIndexBasedAccessor : virtual IComparable { + virtual ~IIndexBasedAccessor() = default; /*! * \brief This method is called to get the item by the specified index * @@ -28,12 +32,12 @@ struct IndexBasedAccessor virtual Value GetItemByIndex(int64_t idx) const = 0; }; -struct ListEnumerator; -using ListEnumeratorPtr = std::unique_ptr; +struct IListEnumerator; +using ListEnumeratorPtr = types::ValuePtr; inline auto MakeEmptyListEnumeratorPtr() { - return ListEnumeratorPtr(nullptr, [](ListEnumerator*) {}); + return ListEnumeratorPtr(); } /*! @@ -45,10 +49,10 @@ inline auto MakeEmptyListEnumeratorPtr() * enumerator to the first element end returns `true` or moves enumerator to the end and returns `false` in case of empty list. Each call of \ref GetCurrent * method should return the current enumerable item. */ -struct ListEnumerator +struct IListEnumerator : virtual IComparable { //! Destructor - virtual ~ListEnumerator() = default; + virtual ~IListEnumerator() = default; /*! * \brief Method is called to reset enumerator to the initial state ('before the first element') if applicable. @@ -103,9 +107,9 @@ struct ListEnumerator * * It's assumed that indexer interface is a part of list implementation. */ -struct ListItemAccessor +struct IListItemAccessor : virtual IComparable { - virtual ~ListItemAccessor() = default; + virtual ~IListItemAccessor() = default; /*! * \brief Called to get pointer to indexer interface implementation (if applicable) @@ -117,7 +121,7 @@ struct ListItemAccessor * * @return Pointer to the indexer interface implementation or null if indexing isn't supported for the list */ - virtual const IndexBasedAccessor* GetIndexer() const = 0; + virtual const IIndexBasedAccessor* GetIndexer() const = 0; /*! * \brief Called to get enumerator of the particular list @@ -154,6 +158,7 @@ struct ListItemAccessor static ListEnumeratorPtr MakeEnumerator(Args&&... args); }; + namespace detail { class GenericListIterator; @@ -174,7 +179,7 @@ class GenericListIterator; * }; * ``` */ -class GenericList +class JINJA2CPP_EXPORT GenericList { public: //! Default constructor @@ -183,7 +188,7 @@ class GenericList /*! * \brief Initializing constructor * - * This constructor is only one way to create the valid GenericList object. `accessor` is a functional object which provides access to the \ref ListItemAccessor + * This constructor is only one way to create the valid GenericList object. `accessor` is a functional object which provides access to the \ref IListItemAccessor * interface. The most common way of GenericList creation is to initialize it with lambda which simultaniously holds and and provide access to the * generic list implementation: * @@ -196,7 +201,7 @@ class GenericList * * @param accessor Functional object which provides access to the particular generic list implementation */ - explicit GenericList(std::function accessor) + explicit GenericList(std::function accessor) : m_accessor(std::move(accessor)) { } @@ -257,15 +262,26 @@ class GenericList */ auto cend() const; - std::function m_accessor; + /*! + * \brief Compares with the objects of same type + * + * @return true if equal + */ + bool IsEqual(const GenericList& rhs) const; + +private: + std::function m_accessor; }; +bool operator==(const GenericList& lhs, const GenericList& rhs); +bool operator!=(const GenericList& lhs, const GenericList& rhs); + template -inline ListEnumeratorPtr ListItemAccessor::MakeEnumerator(Args&& ...args) +inline ListEnumeratorPtr IListItemAccessor::MakeEnumerator(Args&& ...args) { - return ListEnumeratorPtr(new T(std::forward(args)...), [](ListEnumerator* e) { delete e; }); + return ListEnumeratorPtr(types::MakeValuePtr(std::forward(args)...)); } } // namespace jinja2 -#endif // JINJA2CPP_GENERIC_LIST_H \ No newline at end of file +#endif // JINJA2CPP_GENERIC_LIST_H diff --git a/include/jinja2cpp/generic_list_impl.h b/include/jinja2cpp/generic_list_impl.h index 75894547..5ea5124a 100644 --- a/include/jinja2cpp/generic_list_impl.h +++ b/include/jinja2cpp/generic_list_impl.h @@ -11,15 +11,15 @@ namespace jinja2 namespace lists_impl { template -struct InputIteratorListAccessor : ListItemAccessor +struct InputIteratorListAccessor : IListItemAccessor { mutable It1 m_begin; mutable It2 m_end; - struct Enumerator : public ListEnumerator + struct Enumerator : public IListEnumerator { - It1* m_cur; - It2* m_end; + It1* m_cur = nullptr; + It2* m_end = nullptr; bool m_justInited = true; Enumerator(It1* begin, It2* end) @@ -49,7 +49,7 @@ struct InputIteratorListAccessor : ListItemAccessor ListEnumeratorPtr Clone() const override { auto result = MakeEnumerator(m_cur, m_end); - auto ptr = static_cast(result.get()); + auto ptr = static_cast(&(*result)); ptr->m_cur = m_cur; ptr->m_justInited = m_justInited; return result; @@ -59,6 +59,19 @@ struct InputIteratorListAccessor : ListItemAccessor { return MakeEnumerator(std::move(*this)); } + bool IsEqual(const IComparable &other) const override + { + auto* val = dynamic_cast(&other); + if (!val) + return false; + if (m_cur != val->m_cur) + return false; + if (m_end != val->m_end) + return false; + if (m_justInited != val->m_justInited) + return false; + return true; + } }; explicit InputIteratorListAccessor(It1&& b, It2&& e) noexcept @@ -72,7 +85,7 @@ struct InputIteratorListAccessor : ListItemAccessor return nonstd::optional(); } - const IndexBasedAccessor* GetIndexer() const override + const IIndexBasedAccessor* GetIndexer() const override { return nullptr; } @@ -82,15 +95,23 @@ struct InputIteratorListAccessor : ListItemAccessor return MakeEnumerator(&m_begin, &m_end ); } + bool IsEqual(const IComparable& other) const override + { + auto* val = dynamic_cast(&other); + if (!val) + return false; + return m_begin == val->m_begin && m_end == val->m_end; + } + }; template -struct ForwardIteratorListAccessor : ListItemAccessor +struct ForwardIteratorListAccessor : IListItemAccessor { It1 m_begin; It2 m_end; - struct Enumerator : public ListEnumerator + struct Enumerator : public IListEnumerator { It1 m_begin; It1 m_cur; @@ -116,7 +137,9 @@ struct ForwardIteratorListAccessor : ListItemAccessor m_justInited = false; } else - ++ m_cur; + { + ++m_cur; + } return m_cur != m_end; } @@ -129,7 +152,7 @@ struct ForwardIteratorListAccessor : ListItemAccessor ListEnumeratorPtr Clone() const override { auto result = MakeEnumerator(m_cur, m_end); - auto ptr = static_cast(result.get()); + auto ptr = static_cast(&(*result)); ptr->m_begin = m_cur; ptr->m_cur = m_cur; ptr->m_justInited = m_justInited; @@ -140,6 +163,21 @@ struct ForwardIteratorListAccessor : ListItemAccessor { return MakeEnumerator(std::move(*this)); } + bool IsEqual(const IComparable &other) const override + { + auto* val = dynamic_cast(&other); + if (!val) + return false; + if (m_begin != val->m_begin) + return false; + if (m_cur != val->m_cur) + return false; + if (m_end != val->m_end) + return false; + if (m_justInited != val->m_justInited) + return false; + return true; + } }; explicit ForwardIteratorListAccessor(It1&& b, It2&& e) noexcept @@ -153,7 +191,7 @@ struct ForwardIteratorListAccessor : ListItemAccessor return nonstd::optional(); } - const IndexBasedAccessor* GetIndexer() const override + const IIndexBasedAccessor* GetIndexer() const override { return nullptr; } @@ -162,16 +200,22 @@ struct ForwardIteratorListAccessor : ListItemAccessor { return MakeEnumerator(m_begin, m_end); } - + bool IsEqual(const IComparable& other) const override + { + auto* val = dynamic_cast(&other); + if (!val) + return false; + return m_begin == val->m_begin && m_end == val->m_end; + } }; template -struct RandomIteratorListAccessor : ListItemAccessor, IndexBasedAccessor +struct RandomIteratorListAccessor : IListItemAccessor, IIndexBasedAccessor { It1 m_begin; It2 m_end; - struct Enumerator : public ListEnumerator + struct Enumerator : public IListEnumerator { It1 m_begin; It1 m_cur; @@ -197,7 +241,9 @@ struct RandomIteratorListAccessor : ListItemAccessor, IndexBasedAccessor m_justInited = false; } else + { ++ m_cur; + } return m_cur != m_end; } @@ -210,7 +256,7 @@ struct RandomIteratorListAccessor : ListItemAccessor, IndexBasedAccessor ListEnumeratorPtr Clone() const override { auto result = MakeEnumerator(m_cur, m_end); - auto ptr = static_cast(result.get()); + auto ptr = static_cast(&(*result)); ptr->m_begin = m_cur; ptr->m_cur = m_cur; ptr->m_justInited = m_justInited; @@ -221,6 +267,21 @@ struct RandomIteratorListAccessor : ListItemAccessor, IndexBasedAccessor { return MakeEnumerator(std::move(*this)); } + bool IsEqual(const IComparable &other) const override + { + auto* val = dynamic_cast(&other); + if (!val) + return false; + if (m_begin != val->m_begin) + return false; + if (m_cur != val->m_cur) + return false; + if (m_end != val->m_end) + return false; + if (m_justInited != val->m_justInited) + return false; + return true; + } }; explicit RandomIteratorListAccessor(It1 b, It2 e) noexcept @@ -234,7 +295,7 @@ struct RandomIteratorListAccessor : ListItemAccessor, IndexBasedAccessor return std::distance(m_begin, m_end); } - const IndexBasedAccessor* GetIndexer() const override + const IIndexBasedAccessor* GetIndexer() const override { return this; } @@ -252,14 +313,22 @@ struct RandomIteratorListAccessor : ListItemAccessor, IndexBasedAccessor return Reflect(*p); } + bool IsEqual(const IComparable& other) const override + { + auto* val = dynamic_cast(&other); + if (!val) + return false; + return m_begin == val->m_begin && m_end == val->m_end; + } + }; using ListGenerator = std::function()>; -class GeneratedListAccessor : public ListItemAccessor +class GeneratedListAccessor : public IListItemAccessor { public: - class Enumerator : public ListEnumerator + class Enumerator : public IListEnumerator { public: Enumerator(const ListGenerator* fn) @@ -296,10 +365,19 @@ class GeneratedListAccessor : public ListItemAccessor return MakeEnumerator(std::move(*this)); } + bool IsEqual(const IComparable &other) const override + { + auto* val = dynamic_cast(&other); + if (!val) + return false; + return m_fn == val->m_fn && m_current == val->m_current && m_isFinished == val->m_isFinished; + } protected: const ListGenerator* m_fn; Value m_current; bool m_isFinished = false; + + }; explicit GeneratedListAccessor(ListGenerator&& fn) : m_fn(std::move(fn)) {} @@ -308,7 +386,7 @@ class GeneratedListAccessor : public ListItemAccessor { return nonstd::optional(); } - const IndexBasedAccessor* GetIndexer() const override + const IIndexBasedAccessor* GetIndexer() const override { return nullptr; } @@ -317,6 +395,15 @@ class GeneratedListAccessor : public ListItemAccessor { return MakeEnumerator(&m_fn); } + + bool IsEqual(const IComparable& other) const override + { + auto* val = dynamic_cast(&other); + if (!val) + return false; + return m_fn() == val->m_fn(); + } + private: ListGenerator m_fn; }; diff --git a/include/jinja2cpp/generic_list_iterator.h b/include/jinja2cpp/generic_list_iterator.h index d9edb8a5..7af469f8 100644 --- a/include/jinja2cpp/generic_list_iterator.h +++ b/include/jinja2cpp/generic_list_iterator.h @@ -3,13 +3,13 @@ #include "generic_list.h" #include "value.h" -#include "value_ptr.hpp" +#include "value_ptr.h" namespace jinja2 { namespace detail { -class GenericListIterator +class JINJA2CPP_EXPORT GenericListIterator { public: using iterator_category = std::input_iterator_tag; @@ -17,41 +17,33 @@ class GenericListIterator using difference_type = std::ptrdiff_t; using reference = const Value&; using pointer = const Value*; + using EnumeratorPtr = types::ValuePtr; - struct Cloner - { - ListEnumerator* operator()(const ListEnumerator &x) const - { - return x.Clone().release(); - } + GenericListIterator() = default; - ListEnumerator* operator()(ListEnumerator &&x) const - { - return x.Move().release(); - } - }; - - using EnumeratorPtr = nonstd::value_ptr; - - GenericListIterator(ListEnumerator* e = nullptr) - : m_enumerator(e) + GenericListIterator(ListEnumeratorPtr enumerator) + : m_enumerator(types::ValuePtr(enumerator)) { if (m_enumerator) m_hasValue = m_enumerator->MoveNext(); if (m_hasValue) - m_current = std::move(m_enumerator->GetCurrent()); + m_current = m_enumerator->GetCurrent(); } bool operator == (const GenericListIterator& other) const { - if (!this->m_enumerator) - return !other.m_enumerator ? true : other == *this; - - if (!other.m_enumerator) - return !m_hasValue; - - return this->m_enumerator.get() == other.m_enumerator.get(); + if (m_hasValue != other.m_hasValue) + return false; + if (!m_enumerator && !other.m_enumerator) + return true; + if (this->m_enumerator && other.m_enumerator && !m_enumerator->IsEqual(*other.m_enumerator)) + return false; + if ((m_enumerator && !other.m_enumerator) || (!m_enumerator && other.m_enumerator)) + return false; + if (m_current != other.m_current) + return false; + return true; } bool operator != (const GenericListIterator& other) const @@ -66,9 +58,21 @@ class GenericListIterator GenericListIterator& operator ++() { + if (!m_enumerator) + return *this; m_hasValue = m_enumerator->MoveNext(); if (m_hasValue) - m_current = std::move(m_enumerator->GetCurrent()); + { + m_current = m_enumerator->GetCurrent(); + } + else + { + EnumeratorPtr temp; + Value tempVal; + using std::swap; + swap(m_enumerator, temp); + swap(m_current, tempVal); + } return *this; } @@ -87,27 +91,14 @@ class GenericListIterator { } - + private: - const EnumeratorPtr m_enumerator; + EnumeratorPtr m_enumerator; bool m_hasValue = false; Value m_current; }; } // namespace detail - -inline detail::GenericListIterator GenericList::begin() const -{ - return m_accessor && m_accessor() ? detail::GenericListIterator(m_accessor()->CreateEnumerator().release()) : detail::GenericListIterator(); -} - -inline detail::GenericListIterator GenericList::end() const -{ - return detail::GenericListIterator(); -} - -inline auto GenericList::cbegin() const {return begin();} -inline auto GenericList::cend() const {return end();} } // namespace jinja2 -#endif // JINJA2CPP_GENERIC_LIST_ITERATOR_H \ No newline at end of file +#endif // JINJA2CPP_GENERIC_LIST_ITERATOR_H diff --git a/include/jinja2cpp/polymorphic_value.h b/include/jinja2cpp/polymorphic_value.h new file mode 100644 index 00000000..69663d85 --- /dev/null +++ b/include/jinja2cpp/polymorphic_value.h @@ -0,0 +1,447 @@ +/* + +Copyright (c) 2016 Jonathan B. Coe + +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 ISOCPP_P0201_POLYMORPHIC_VALUE_H_INCLUDED +#define ISOCPP_P0201_POLYMORPHIC_VALUE_H_INCLUDED + +#include +#include +#include +#include +#include +#include + + +// +// in_place: code duplicated in any-lite, expected-lite, optional-lite, value-ptr-lite, variant-lite: +// + +#ifndef nonstd_lite_HAVE_IN_PLACE_TYPES +#define nonstd_lite_HAVE_IN_PLACE_TYPES 1 + +// C++17 std::in_place in : + +#if variant_CPP17_OR_GREATER + +#include + +namespace nonstd { + +using std::in_place; +using std::in_place_type; +using std::in_place_index; +using std::in_place_t; +using std::in_place_type_t; +using std::in_place_index_t; + +#define nonstd_lite_in_place_t( T) std::in_place_t +#define nonstd_lite_in_place_type_t( T) std::in_place_type_t +#define nonstd_lite_in_place_index_t(K) std::in_place_index_t + +#define nonstd_lite_in_place( T) std::in_place_t{} +#define nonstd_lite_in_place_type( T) std::in_place_type_t{} +#define nonstd_lite_in_place_index(K) std::in_place_index_t{} + +} // namespace nonstd + +#else // variant_CPP17_OR_GREATER + +#include + +namespace nonstd { +namespace detail { + +template< class T > +struct in_place_type_tag {}; + +template< std::size_t K > +struct in_place_index_tag {}; + +} // namespace detail + +struct in_place_t {}; + +template< class T > +inline in_place_t in_place( detail::in_place_type_tag = detail::in_place_type_tag() ) +{ + return in_place_t(); +} + +template< std::size_t K > +inline in_place_t in_place( detail::in_place_index_tag = detail::in_place_index_tag() ) +{ + return in_place_t(); +} + +template< class T > +inline in_place_t in_place_type( detail::in_place_type_tag = detail::in_place_type_tag() ) +{ + return in_place_t(); +} + +template< std::size_t K > +inline in_place_t in_place_index( detail::in_place_index_tag = detail::in_place_index_tag() ) +{ + return in_place_t(); +} + +// mimic templated typedef: + +#define nonstd_lite_in_place_t( T) nonstd::in_place_t(&)( nonstd::detail::in_place_type_tag ) +#define nonstd_lite_in_place_type_t( T) nonstd::in_place_t(&)( nonstd::detail::in_place_type_tag ) +#define nonstd_lite_in_place_index_t(K) nonstd::in_place_t(&)( nonstd::detail::in_place_index_tag ) + +#define nonstd_lite_in_place( T) nonstd::in_place_type +#define nonstd_lite_in_place_type( T) nonstd::in_place_type +#define nonstd_lite_in_place_index(K) nonstd::in_place_index + +} // namespace nonstd + +#endif // variant_CPP17_OR_GREATER +#endif // nonstd_lite_HAVE_IN_PLACE_TYPES + +namespace isocpp_p0201 { + +namespace detail { + +//////////////////////////////////////////////////////////////////////////// +// Implementation detail classes +//////////////////////////////////////////////////////////////////////////// + +template +struct default_copy { + T* operator()(const T& t) const { return new T(t); } +}; + +template +struct default_delete { + void operator()(const T* t) const { delete t; } +}; + +template +struct control_block { + virtual ~control_block() = default; + + virtual std::unique_ptr clone() const = 0; + + virtual T* ptr() = 0; +}; + +template +class direct_control_block : public control_block { + static_assert(!std::is_reference::value, ""); + U u_; + + public: + template + explicit direct_control_block(Ts&&... ts) : u_(U(std::forward(ts)...)) {} + + std::unique_ptr> clone() const override { + return std::make_unique(*this); + } + + T* ptr() override { return std::addressof(u_); } +}; + +template , + class D = default_delete> +class pointer_control_block : public control_block, public C { + std::unique_ptr p_; + + public: + explicit pointer_control_block(U* u, C c = C{}, D d = D{}) + : C(std::move(c)), p_(u, std::move(d)) {} + + explicit pointer_control_block(std::unique_ptr p, C c = C{}) + : C(std::move(c)), p_(std::move(p)) {} + + std::unique_ptr> clone() const override { + assert(p_); + return std::make_unique( + C::operator()(*p_), static_cast(*this), p_.get_deleter()); + } + + T* ptr() override { return p_.get(); } +}; + +template +class delegating_control_block : public control_block { + std::unique_ptr> delegate_; + + public: + explicit delegating_control_block(std::unique_ptr> b) + : delegate_(std::move(b)) {} + + std::unique_ptr> clone() const override { + return std::make_unique(delegate_->clone()); + } + + T* ptr() override { return delegate_->ptr(); } +}; + +} // end namespace detail + +class bad_polymorphic_value_construction : public std::exception { + public: + bad_polymorphic_value_construction() noexcept = default; + + const char* what() const noexcept override { + return "Dynamic and static type mismatch in polymorphic_value " + "construction"; + } +}; + +template +class polymorphic_value; + +template +struct is_polymorphic_value : std::false_type {}; + +template +struct is_polymorphic_value> : std::true_type {}; + +//////////////////////////////////////////////////////////////////////////////// +// `polymorphic_value` class definition +//////////////////////////////////////////////////////////////////////////////// + +template +class polymorphic_value { + static_assert(!std::is_union::value, ""); + static_assert(std::is_class::value, ""); + + template + friend class polymorphic_value; + + template + friend polymorphic_value make_polymorphic_value(Ts&&... ts); + template + friend polymorphic_value make_polymorphic_value(Ts&&... ts); + + T* ptr_ = nullptr; + std::unique_ptr> cb_; + + public: + // + // Destructor + // + + ~polymorphic_value() = default; + + // + // Constructors + // + + polymorphic_value() {} + + template , + class D = detail::default_delete, + class V = std::enable_if_t::value>> + explicit polymorphic_value(U* u, C copier = C{}, D deleter = D{}) { + if (!u) { + return; + } + +#ifndef ISOCPP_P0201_POLYMORPHIC_VALUE_NO_RTTI + if (std::is_same>::value && + std::is_same>::value && + typeid(*u) != typeid(U)) + throw bad_polymorphic_value_construction(); +#endif + std::unique_ptr p(u, std::move(deleter)); + + cb_ = std::make_unique>( + std::move(p), std::move(copier)); + ptr_ = u; + } + + // + // Copy-constructors + // + + polymorphic_value(const polymorphic_value& p) { + if (!p) { + return; + } + auto tmp_cb = p.cb_->clone(); + ptr_ = tmp_cb->ptr(); + cb_ = std::move(tmp_cb); + } + + // + // Move-constructors + // + + polymorphic_value(polymorphic_value&& p) noexcept { + ptr_ = p.ptr_; + cb_ = std::move(p.cb_); + p.ptr_ = nullptr; + } + + // + // Converting constructors + // + + template ::value && + std::is_convertible::value>> + explicit polymorphic_value(const polymorphic_value& p) { + polymorphic_value tmp(p); + ptr_ = tmp.ptr_; + cb_ = std::make_unique>( + std::move(tmp.cb_)); + } + + template ::value && + std::is_convertible::value>> + explicit polymorphic_value(polymorphic_value&& p) { + ptr_ = p.ptr_; + cb_ = std::make_unique>( + std::move(p.cb_)); + p.ptr_ = nullptr; + } + +#if __cplusplus < 201703L + +#endif + // + // In-place constructor + // + + template *, T*>::value && + !is_polymorphic_value>::value>, + class... Ts> + explicit polymorphic_value(nonstd_lite_in_place_type_t(U), Ts&&... ts) +// explicit polymorphic_value(std::in_place_type_t, Ts&&... ts) + : cb_(std::make_unique>( + std::forward(ts)...)) { + ptr_ = cb_->ptr(); + } + + + // + // Assignment + // + + polymorphic_value& operator=(const polymorphic_value& p) { + if (std::addressof(p) == this) { + return *this; + } + + if (!p) { + cb_.reset(); + ptr_ = nullptr; + return *this; + } + + auto tmp_cb = p.cb_->clone(); + ptr_ = tmp_cb->ptr(); + cb_ = std::move(tmp_cb); + return *this; + } + + // + // Move-assignment + // + + polymorphic_value& operator=(polymorphic_value&& p) noexcept { + if (std::addressof(p) == this) { + return *this; + } + + cb_ = std::move(p.cb_); + ptr_ = p.ptr_; + p.ptr_ = nullptr; + return *this; + } + + // + // Modifiers + // + + void swap(polymorphic_value& p) noexcept { + using std::swap; + swap(ptr_, p.ptr_); + swap(cb_, p.cb_); + } + + // + // Observers + // + + explicit operator bool() const { return bool(cb_); } + + const T* operator->() const { + assert(ptr_); + return ptr_; + } + + const T& operator*() const { + assert(*this); + return *ptr_; + } + + T* operator->() { + assert(*this); + return ptr_; + } + + T& operator*() { + assert(*this); + return *ptr_; + } +}; + +// +// polymorphic_value creation +// +template +polymorphic_value make_polymorphic_value(Ts&&... ts) { + polymorphic_value p; + p.cb_ = std::make_unique>( + std::forward(ts)...); + p.ptr_ = p.cb_->ptr(); + return p; +} +template +polymorphic_value make_polymorphic_value(Ts&&... ts) { + polymorphic_value p; + p.cb_ = std::make_unique>( + std::forward(ts)...); + p.ptr_ = p.cb_->ptr(); + return p; +} + +// +// non-member swap +// +template +void swap(polymorphic_value& t, polymorphic_value& u) noexcept { + t.swap(u); +} + +} // namespace isocpp_p0201 + +#endif // ISOCPP_P0201_POLYMORPHIC_VALUE_H_INCLUDED diff --git a/include/jinja2cpp/reflected_value.h b/include/jinja2cpp/reflected_value.h index b6519961..4947fa16 100644 --- a/include/jinja2cpp/reflected_value.h +++ b/include/jinja2cpp/reflected_value.h @@ -85,7 +85,7 @@ struct TypeReflection : TypeReflectedImpl #ifndef JINJA2CPP_NO_DOXYGEN template -class ReflectedMapImplBase : public MapItemAccessor +class ReflectedMapImplBase : public IMapItemAccessor { public: bool HasValue(const std::string& name) const override @@ -158,6 +158,7 @@ class ReflectedMapImpl : public ReflectedMapImplBase>, publi { public: using ReflectedDataHolder::ReflectedDataHolder; + using ThisType = ReflectedMapImpl; static auto GetAccessors() {return TypeReflection::GetAccessors();} template @@ -168,6 +169,15 @@ class ReflectedMapImpl : public ReflectedMapImplBase>, publi return Value(); return accessor(*v); } + + bool IsEqual(const IComparable& other) const override + { + auto* val = dynamic_cast(&other); + if (!val) + return false; + + return this->GetValue() == val->GetValue(); + } }; namespace detail @@ -179,8 +189,9 @@ template using IsReflectedType = std::enable_if_t::value>; template -struct Enumerator : public ListEnumerator +struct Enumerator : public IListEnumerator { + using ThisType = Enumerator; It m_begin; It m_cur; It m_end; @@ -205,7 +216,9 @@ struct Enumerator : public ListEnumerator m_justInited = false; } else + { ++ m_cur; + } return m_cur != m_end; } @@ -220,7 +233,7 @@ struct Enumerator : public ListEnumerator auto result = std::make_unique>(m_begin, m_end); result->m_cur = m_cur; result->m_justInited = m_justInited; - return jinja2::ListEnumeratorPtr(result.release(), Deleter); + return jinja2::ListEnumeratorPtr(result.release()); //, Deleter); } ListEnumeratorPtr Move() override @@ -229,20 +242,46 @@ struct Enumerator : public ListEnumerator result->m_cur = std::move(m_cur); result->m_justInited = m_justInited; this->m_justInited = true; - return jinja2::ListEnumeratorPtr(result.release(), Deleter); + return jinja2::ListEnumeratorPtr(result.release()); //, Deleter); } - static void Deleter(ListEnumerator* e) + bool IsEqual(const IComparable& other) const override + { + auto* val = dynamic_cast(&other); + if (!val) + return false; + if (m_begin != val->m_begin) + return false; + if (m_cur != val->m_cur) + return false; + if (m_end != val->m_end) + return false; + if (m_justInited != val->m_justInited) + return false; + return true; + } + + /* + It m_begin; + It m_cur; + It m_end; + bool m_justInited = true; +*/ +/* + static void Deleter(IListEnumerator* e) { delete static_cast*>(e); } + */ }; struct ContainerReflector { template - struct ValueItemAccessor : ListItemAccessor, IndexBasedAccessor + struct ValueItemAccessor : IListItemAccessor, IIndexBasedAccessor { + using ThisType = ValueItemAccessor; + T m_value; explicit ValueItemAccessor(T&& cont) noexcept @@ -255,7 +294,7 @@ struct ContainerReflector return m_value.size(); } - const IndexBasedAccessor* GetIndexer() const override + const IIndexBasedAccessor* GetIndexer() const override { return this; } @@ -263,7 +302,7 @@ struct ContainerReflector ListEnumeratorPtr CreateEnumerator() const override { using Enum = Enumerator; - return jinja2::ListEnumeratorPtr(new Enum(m_value.begin(), m_value.end()), Enum::Deleter); + return jinja2::ListEnumeratorPtr(new Enum(m_value.begin(), m_value.end()));//, Enum::Deleter); } Value GetItemByIndex(int64_t idx) const override @@ -272,12 +311,26 @@ struct ContainerReflector std::advance(p, static_cast(idx)); return Reflect(*p); } + + bool IsEqual(const IComparable& other) const override + { + auto* val = dynamic_cast(&other); + if (!val) + return false; + auto enumerator = CreateEnumerator(); + auto otherEnum = val->CreateEnumerator(); + if (enumerator && otherEnum && !enumerator->IsEqual(*otherEnum)) + return false; + return true; + } }; template - struct PtrItemAccessor : ListItemAccessor, IndexBasedAccessor + struct PtrItemAccessor : IListItemAccessor, IIndexBasedAccessor { - const T* m_value; + using ThisType = PtrItemAccessor; + + const T* m_value{}; explicit PtrItemAccessor(const T* ptr) : m_value(ptr) @@ -287,7 +340,7 @@ struct ContainerReflector { return m_value->size(); } - const IndexBasedAccessor* GetIndexer() const override + const IIndexBasedAccessor* GetIndexer() const override { return this; } @@ -295,7 +348,7 @@ struct ContainerReflector ListEnumeratorPtr CreateEnumerator() const override { using Enum = Enumerator; - return jinja2::ListEnumeratorPtr(new Enum(m_value->begin(), m_value->end()), Enum::Deleter); + return jinja2::ListEnumeratorPtr(new Enum(m_value->begin(), m_value->end()));//, Enum::Deleter); } Value GetItemByIndex(int64_t idx) const override @@ -304,6 +357,18 @@ struct ContainerReflector std::advance(p, static_cast(idx)); return Reflect(*p); } + + bool IsEqual(const IComparable& other) const override + { + auto* val = dynamic_cast(&other); + if (!val) + return false; + auto enumerator = CreateEnumerator(); + auto otherEnum = val->CreateEnumerator(); + if (enumerator && otherEnum && !enumerator->IsEqual(*otherEnum)) + return false; + return true; + } }; template diff --git a/include/jinja2cpp/template.h b/include/jinja2cpp/template.h index b17929b9..687fc060 100644 --- a/include/jinja2cpp/template.h +++ b/include/jinja2cpp/template.h @@ -150,11 +150,21 @@ class JINJA2CPP_EXPORT Template */ Result> GetMetadataRaw(); + /* ! + * \brief compares to an other object of the same type + * + * @return true if equal + */ + bool IsEqual(const Template& other) const; + private: std::shared_ptr m_impl; friend class TemplateImpl; }; +bool operator==(const Template& lhs, const Template& rhs); +bool operator!=(const Template& lhs, const Template& rhs); + /*! * \brief Template object which is used to render wide char templates * @@ -275,10 +285,21 @@ class JINJA2CPP_EXPORT TemplateW */ ResultW> GetMetadataRaw(); + /* ! + * \brief compares to an other object of the same type + * + * @return true if equal + */ + bool IsEqual(const TemplateW& other) const; + private: std::shared_ptr m_impl; friend class TemplateImpl; }; + +bool operator==(const TemplateW& lhs, const TemplateW& rhs); +bool operator!=(const TemplateW& lhs, const TemplateW& rhs); + } // namespace jinja2 #endif // JINJA2CPP_TEMPLATE_H diff --git a/include/jinja2cpp/template_env.h b/include/jinja2cpp/template_env.h index d0ec6dc6..09a6a52c 100644 --- a/include/jinja2cpp/template_env.h +++ b/include/jinja2cpp/template_env.h @@ -50,6 +50,17 @@ struct Settings std::string m_defaultMetadataType = "json"; }; +inline bool operator==(const Settings& lhs, const Settings& rhs) +{ + auto lhsTie = std::tie(lhs.useLineStatements, lhs.trimBlocks, lhs.lstripBlocks, lhs.cacheSize, lhs.autoReload, lhs.extensions.Do, lhs.jinja2CompatMode, lhs.m_defaultMetadataType); + auto rhsTie = std::tie(rhs.useLineStatements, rhs.trimBlocks, rhs.lstripBlocks, rhs.cacheSize, rhs.autoReload, rhs.extensions.Do, rhs.jinja2CompatMode, rhs.m_defaultMetadataType); + return lhsTie == rhsTie; +} +inline bool operator!=(const Settings& lhs, const Settings& rhs) +{ + return !(lhs == rhs); +} + /*! * \brief Global template environment which controls behaviour of the different \ref Template instances * @@ -205,6 +216,22 @@ class JINJA2CPP_EXPORT TemplateEnv fn(m_globalValues); } + bool IsEqual(const TemplateEnv& other) const + { + if (m_filesystemHandlers != other.m_filesystemHandlers) + return false; + if (m_settings != other.m_settings) + return false; + if (m_globalValues != other.m_globalValues) + return false; + if (m_templateCache != other.m_templateCache) + return false; + if (m_templateWCache != other.m_templateWCache) + return false; + + return true; + } + private: template auto LoadTemplateImpl(TemplateEnv* env, std::string fileName, const T& filesystemHandlers, Cache& cache); @@ -215,6 +242,20 @@ class JINJA2CPP_EXPORT TemplateEnv { std::string prefix; FilesystemHandlerPtr handler; + bool operator==(const FsHandler& rhs) const + { + if (prefix != rhs.prefix) + return false; + if (handler && rhs.handler && !handler->IsEqual(*rhs.handler)) + return false; + if ((!handler && rhs.handler) || (handler && !rhs.handler)) + return false; + return true; + } + bool operator!=(const FsHandler& rhs) const + { + return !(*this == rhs); + } }; struct BaseTemplateInfo @@ -222,16 +263,48 @@ class JINJA2CPP_EXPORT TemplateEnv nonstd::optional lastModification; TimeStamp lastAccessTime; FilesystemHandlerPtr handler; + bool operator==(const BaseTemplateInfo& other) const + { + if (lastModification != other.lastModification) + return false; + if (lastAccessTime != other.lastAccessTime) + return false; + if (handler && other.handler && !handler->IsEqual(*other.handler)) + return false; + if ((!handler && other.handler) || (handler && !other.handler)) + return false; + return true; + } + bool operator!=(const BaseTemplateInfo& other) const + { + return !(*this == other); + } }; struct TemplateCacheEntry : public BaseTemplateInfo { Template tpl; + bool operator==(const TemplateCacheEntry& other) const + { + return BaseTemplateInfo::operator==(other) && tpl == other.tpl; + } + bool operator!=(const TemplateCacheEntry& other) const + { + return !(*this == other); + } }; struct TemplateWCacheEntry : public BaseTemplateInfo { TemplateW tpl; + bool operator==(const TemplateWCacheEntry& other) const + { + return BaseTemplateInfo::operator==(other) && tpl == other.tpl; + } + bool operator!=(const TemplateWCacheEntry& other) const + { + return !(*this == other); + } }; std::vector m_filesystemHandlers; diff --git a/include/jinja2cpp/user_callable.h b/include/jinja2cpp/user_callable.h index c287c059..dae15f3d 100644 --- a/include/jinja2cpp/user_callable.h +++ b/include/jinja2cpp/user_callable.h @@ -6,6 +6,7 @@ #include +#include #include #include @@ -79,12 +80,12 @@ struct ArgPromoter, void> operator const string&() const { return *m_ptr; } operator string() const { return *m_ptr; } operator string_view () const { return *m_ptr; } - operator other_string () const - { + operator other_string () const + { return ConvertString(*m_ptr); } - operator other_string_view () const - { + operator other_string_view () const + { m_convertedStr = ConvertString(*m_ptr); return m_convertedStr.value(); } @@ -182,7 +183,7 @@ inline const Value& GetParamValue(const UserCallableParams& params, const ArgInf template struct ParamUnwrapper { - V* m_visitor; + V* m_visitor{}; ParamUnwrapper(V* v) : m_visitor(v) @@ -198,7 +199,9 @@ struct ParamUnwrapper template static auto& UnwrapRecursive(const RecWrapper& arg) { - return arg.value(); + if (!arg) + throw std::runtime_error("No value to unwrap"); + return *arg; } template @@ -377,10 +380,11 @@ struct ArgDescrHasType...> : std::true_type template auto MakeCallable(Fn&& f, ArgDescr&&... ad) -> typename std::enable_if::value, UserCallable>::type { + UserCallable::UserCallableFunctionPtr callable = [=, fn = std::forward(f)](const UserCallableParams& params) { + return detail::InvokeUserCallable(fn, params, ad...); + }; return UserCallable { - [=, fn = std::forward(f)](const UserCallableParams& params) { - return detail::InvokeUserCallable(fn, params, ad...); - }, + callable, {ArgInfo(std::forward(ad))...} }; } @@ -388,35 +392,51 @@ auto MakeCallable(Fn&& f, ArgDescr&&... ad) -> typename std::enable_if auto MakeCallable(Fn&& f, ArgDescr&&... ad) -> typename std::enable_if::value, UserCallable>::type { - return UserCallable{ [=, fn = std::forward(f)](const UserCallableParams& params) { return detail::InvokeTypedUserCallable(fn, params, ad...); }, - { ArgInfo(std::forward(ad))... } }; + UserCallable::UserCallableFunctionPtr callable = [=, fn = std::forward(f)](const UserCallableParams& params) { + return detail::InvokeTypedUserCallable(fn, params, ad...); + }; + return UserCallable { + callable, + { ArgInfo(std::forward(ad))... } + }; } template auto MakeCallable(R (*f)(Args...), ArgDescr&&... ad) -> UserCallable { - return UserCallable{ [=, fn = f](const UserCallableParams& params) { return detail::InvokeTypedUserCallable(fn, params, ArgInfoT(ad)...); }, - { ArgInfoT(std::forward(ad))... } }; + UserCallable::UserCallableFunctionPtr callable = [=, fn = f](const UserCallableParams& params) { + return detail::InvokeTypedUserCallable(fn, params, ArgInfoT(ad)...); + }; + return UserCallable { + callable, + { ArgInfoT(std::forward(ad))... } + }; } template auto MakeCallable(R (T::*f)(Args...), T* obj, ArgDescr&&... ad) -> UserCallable { - return UserCallable{ [=, fn = f](const UserCallableParams& params) { - return detail::InvokeTypedUserCallable( - [fn, obj](Args&&... args) { return (obj->*fn)(std::forward(args)...); }, params, ArgInfoT(ad)...); - }, - { ArgInfoT(std::forward(ad))... } }; + UserCallable::UserCallableFunctionPtr callable = [=, fn = f](const UserCallableParams& params) { + return detail::InvokeTypedUserCallable( + [fn, obj](Args&&... args) { return (obj->*fn)(std::forward(args)...); }, params, ArgInfoT(ad)...); + }; + return UserCallable { + callable, + { ArgInfoT(std::forward(ad))... } + }; } template auto MakeCallable(R (T::*f)(Args...) const, const T* obj, ArgDescr&&... ad) -> UserCallable { - return UserCallable{ [=, fn = f](const UserCallableParams& params) { - return detail::InvokeTypedUserCallable( - [fn, obj](Args&&... args) { return (obj->*fn)(std::forward(args)...); }, params, ArgInfoT(ad)...); - }, - { ArgInfoT(std::forward(ad))... } }; + UserCallable::UserCallableFunctionPtr callable = [=, fn = f](const UserCallableParams& params) { + return detail::InvokeTypedUserCallable( + [fn, obj](Args&&... args) { return (obj->*fn)(std::forward(args)...); }, params, ArgInfoT(ad)...); + }; + return UserCallable { + callable, + { ArgInfoT(std::forward(ad))... } + }; } /*! diff --git a/include/jinja2cpp/utils/i_comparable.h b/include/jinja2cpp/utils/i_comparable.h new file mode 100644 index 00000000..76f58d5c --- /dev/null +++ b/include/jinja2cpp/utils/i_comparable.h @@ -0,0 +1,16 @@ +#ifndef JINJA2CPP_ICOMPARABLE_H +#define JINJA2CPP_ICOMPARABLE_H + +#include + +namespace jinja2 { + +struct JINJA2CPP_EXPORT IComparable +{ + virtual ~IComparable() {} + virtual bool IsEqual(const IComparable& other) const = 0; +}; + +} // namespace jinja2 + +#endif // JINJA2CPP_ICOMPARABLE_H diff --git a/include/jinja2cpp/value.h b/include/jinja2cpp/value.h index 0394a02f..a22c33ac 100644 --- a/include/jinja2cpp/value.h +++ b/include/jinja2cpp/value.h @@ -1,18 +1,21 @@ #ifndef JINJA2CPP_VALUE_H #define JINJA2CPP_VALUE_H -#include "generic_list.h" -#include "value_ptr.hpp" +#include +#include +#include #include #include #include +#include #include #include #include #include #include +#include namespace jinja2 { @@ -22,15 +25,23 @@ struct EmptyValue template operator T() const {return T{};} }; + +inline bool operator==(const EmptyValue& lhs, const EmptyValue& rhs) +{ + (void)lhs; + (void)rhs; + return true; +} + class Value; /*! * \brief Interface to the generic dictionary type which maps string to some value */ -struct MapItemAccessor +struct IMapItemAccessor : IComparable { //! Destructor - virtual ~MapItemAccessor() = default; + virtual ~IMapItemAccessor() = default; //! Method is called to obtain number of items in the dictionary. Maximum possible size_t value means non-calculable size virtual size_t GetSize() const = 0; @@ -57,14 +68,21 @@ struct MapItemAccessor * @return Collection of keys if any. Ordering of keys is unspecified. */ virtual std::vector GetKeys() const = 0; + + /*! + * \brief Compares to object of the same type + * + * @return true if equal + */ +// virtual bool IsEqual(const IMapItemAccessor& rhs) const = 0; }; /*! - * \brief Helper class for accessing maps specified by the \ref MapItemAccessor interface + * \brief Helper class for accessing maps specified by the \ref IMapItemAccessor interface * * In the \ref Value type can be stored either ValuesMap instance or GenericMap instance. ValuesMap is a simple * dictionary object based on std::unordered_map. Rather than GenericMap is a more robust object which can provide - * access to the different types of dictionary entities. GenericMap takes the \ref MapItemAccessor interface instance + * access to the different types of dictionary entities. GenericMap takes the \ref IMapItemAccessor interface instance * and uses it to access particular items in the dictionaries. */ class GenericMap @@ -76,12 +94,12 @@ class GenericMap /*! * \brief Initializing constructor * - * The only one way to get valid non-empty GeneridMap is to construct it with the specified \ref MapItemAccessor + * The only one way to get valid non-empty GeneridMap is to construct it with the specified \ref IMapItemAccessor * implementation provider. This provider is a functional object which returns pointer to the interface instance. * - * @param accessor Functional object which returns pointer to the \ref MapItemAccessor interface + * @param accessor Functional object which returns pointer to the \ref IMapItemAccessor interface */ - explicit GenericMap(std::function accessor) + explicit GenericMap(std::function accessor) : m_accessor(std::move(accessor)) { } @@ -137,9 +155,12 @@ class GenericMap auto operator[](const std::string& name) const; private: - std::function m_accessor; + std::function m_accessor; }; +bool operator==(const GenericMap& lhs, const GenericMap& rhs); +bool operator!=(const GenericMap& lhs, const GenericMap& rhs); + using ValuesList = std::vector; struct ValuesMap; struct UserCallableArgs; @@ -147,7 +168,7 @@ struct ParamInfo; struct UserCallable; template -using RecWrapper = nonstd::value_ptr; +using RecWrapper = types::ValuePtr; /*! * \brief Generic value class @@ -209,9 +230,9 @@ class Value ~Value(); //! Assignment operator - Value& operator =(const Value&); + Value& operator=(const Value&); //! Move assignment operator - Value& operator =(Value&&) noexcept; + Value& operator=(Value&&) noexcept; /*! * \brief Generic initializing constructor * @@ -408,7 +429,7 @@ class Value */ auto& asList() { - return *nonstd::get>(m_data).get(); + return *nonstd::get>(m_data); } /*! * \brief Returns non-mutable containing jinja2::ValuesList object @@ -419,7 +440,7 @@ class Value */ auto& asList() const { - return *nonstd::get>(m_data).get(); + return *nonstd::get>(m_data); } //! Test Value for containing jinja2::ValuesMap object bool isMap() const @@ -435,7 +456,7 @@ class Value */ auto& asMap() { - return *nonstd::get>(m_data).get(); + return *nonstd::get>(m_data); } /*! * \brief Returns non-mutable containing jinja2::ValuesMap object @@ -446,7 +467,7 @@ class Value */ auto& asMap() const { - return *nonstd::get>(m_data).get(); + return *nonstd::get>(m_data); } template @@ -479,15 +500,27 @@ class Value return nonstd::get_if(&m_data) != nullptr; } + bool IsEqual(const Value& rhs) const; + private: ValueData m_data; }; +JINJA2CPP_EXPORT bool operator==(const Value& lhs, const Value& rhs); +JINJA2CPP_EXPORT bool operator!=(const Value& lhs, const Value& rhs); +bool operator==(const types::ValuePtr& lhs, const types::ValuePtr& rhs); +bool operator!=(const types::ValuePtr& lhs, const types::ValuePtr& rhs); +bool operator==(const types::ValuePtr>& lhs, const types::ValuePtr>& rhs); +bool operator!=(const types::ValuePtr>& lhs, const types::ValuePtr>& rhs); + struct ValuesMap : std::unordered_map { using unordered_map::unordered_map; }; +bool operator==(const types::ValuePtr& lhs, const types::ValuePtr& rhs); +bool operator!=(const types::ValuePtr& lhs, const types::ValuePtr& rhs); + /*! * \brief Information about user-callable parameters passed from Jinja2 call context * @@ -538,6 +571,20 @@ struct ArgInfo , defValue(std::move(defVal)) {} }; +inline bool operator==(const ArgInfo& lhs, const ArgInfo& rhs) +{ + if (lhs.paramName != rhs.paramName) + return false; + if (lhs.isMandatory != rhs.isMandatory) + return false; + return lhs.defValue == rhs.defValue; +} + +inline bool operator!=(const ArgInfo& lhs, const ArgInfo& rhs) +{ + return !(lhs == rhs); +} + template struct ArgInfoT : public ArgInfo { @@ -592,21 +639,80 @@ struct ArgInfoT : public ArgInfo * If any of argument, marked as `mandatory` in the \ref UserCallable::argsInfo field is missed in the point of the * user-defined call the call is failed. */ -struct UserCallable +struct JINJA2CPP_EXPORT UserCallable { + using UserCallableFunctionPtr = std::function; + UserCallable() : m_counter(++m_gen) {} + UserCallable(const UserCallableFunctionPtr& fptr, const std::vector& argsInfos) + : callable(fptr) + , argsInfo(argsInfos) + , m_counter(++m_gen) + { + } + UserCallable(const UserCallable& other) + : callable(other.callable) + , argsInfo(other.argsInfo) + , m_counter(other.m_counter) + { + } + UserCallable& operator=(const UserCallable& other) + { + if (*this == other) + return *this; + UserCallable temp(other); + + using std::swap; + swap(callable, temp.callable); + swap(argsInfo, temp.argsInfo); + swap(m_counter, temp.m_counter); + + return *this; + } + + UserCallable(UserCallable&& other) noexcept + : callable(std::move(other.callable)) + , argsInfo(std::move(other.argsInfo)) + , m_counter(other.m_counter) + { + } + + UserCallable& operator=(UserCallable&& other) noexcept + { + callable = std::move(other.callable); + argsInfo = std::move(other.argsInfo); + m_counter = other.m_counter; + + return *this; + } + + bool IsEqual(const UserCallable& other) const + { + return m_counter == other.m_counter; + } + //! Functional object which is actually handle the call - std::function callable; + UserCallableFunctionPtr callable; //! Information about arguments of the user-defined callable std::vector argsInfo; + +private: + static std::atomic_uint64_t m_gen; + uint64_t m_counter{}; }; +bool operator==(const UserCallable& lhs, const UserCallable& rhs); +bool operator!=(const UserCallable& lhs, const UserCallable& rhs); +bool operator==(const types::ValuePtr& lhs, const types::ValuePtr& rhs); +bool operator!=(const types::ValuePtr& lhs, const types::ValuePtr& rhs); + + inline Value::Value(const UserCallable& callable) - : m_data(RecWrapper(callable)) + : m_data(types::MakeValuePtr(callable)) { } inline Value::Value(UserCallable&& callable) - : m_data(RecWrapper(std::move(callable))) + : m_data(types::MakeValuePtr(std::move(callable))) { } @@ -636,19 +742,19 @@ inline Value& Value::operator=(Value&& val) noexcept return *this; } inline Value::Value(const ValuesMap& map) - : m_data(RecWrapper(map)) + : m_data(types::MakeValuePtr(map)) { } inline Value::Value(const ValuesList& list) - : m_data(RecWrapper(list)) + : m_data(types::MakeValuePtr(list)) { } inline Value::Value(ValuesList&& list) noexcept - : m_data(RecWrapper(std::move(list))) + : m_data(types::MakeValuePtr(std::move(list))) { } inline Value::Value(ValuesMap&& map) noexcept - : m_data(RecWrapper(std::move(map))) + : m_data(types::MakeValuePtr(std::move(map))) { } diff --git a/include/jinja2cpp/value_ptr.h b/include/jinja2cpp/value_ptr.h new file mode 100644 index 00000000..26eee6b6 --- /dev/null +++ b/include/jinja2cpp/value_ptr.h @@ -0,0 +1,29 @@ +#ifndef JINJA2CPP_VALUE_PTR_H +#define JINJA2CPP_VALUE_PTR_H + +#include "polymorphic_value.h" + +namespace jinja2 { +namespace types { + +using namespace isocpp_p0201; + +template +using ValuePtr = polymorphic_value; + +template +ValuePtr MakeValuePtr(Ts&&... ts) +{ + return make_polymorphic_value(std::forward(ts)...); +} + +template +ValuePtr MakeValuePtr(Ts&&... ts) +{ + return make_polymorphic_value(std::forward(ts)...); +} + +} // namespace types +} // namespace jinja2 + +#endif // JINJA2CPP_VALUE_PTR_H diff --git a/include/jinja2cpp/value_ptr.hpp b/include/jinja2cpp/value_ptr.hpp deleted file mode 100644 index bdda810a..00000000 --- a/include/jinja2cpp/value_ptr.hpp +++ /dev/null @@ -1,1295 +0,0 @@ -#ifndef JINJA2CPP_VALUE_PTR_HPP -#define JINJA2CPP_VALUE_PTR_HPP - -// -// Copyright 2017-2018 by Martin Moene -// -// https://github.com/martinmoene/value-ptr-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_VALUE_PTR_LITE_HPP -#define NONSTD_VALUE_PTR_LITE_HPP - -#define value_ptr_lite_MAJOR 0 -#define value_ptr_lite_MINOR 2 -#define value_ptr_lite_PATCH 1 - -#define value_ptr_lite_VERSION nsvp_STRINGIFY(value_ptr_lite_MAJOR) "." nsvp_STRINGIFY(value_ptr_lite_MINOR) "." nsvp_STRINGIFY(value_ptr_lite_PATCH) - -#define nsvp_STRINGIFY( x ) nsvp_STRINGIFY_( x ) -#define nsvp_STRINGIFY_( x ) #x - -// value-ptr-lite configuration: - -#ifndef nsvp_CONFIG_COMPARE_POINTERS -# define nsvp_CONFIG_COMPARE_POINTERS 0 -#endif - -// Control presence of exception handling (try and auto discover): - -#ifndef nsvp_CONFIG_NO_EXCEPTIONS -# if defined(__cpp_exceptions) || defined(__EXCEPTIONS) || defined(_CPPUNWIND) -# define nsvp_CONFIG_NO_EXCEPTIONS 0 -# else -# define nsvp_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 nsvp_CPLUSPLUS -# if defined(_MSVC_LANG ) && !defined(__clang__) -# define nsvp_CPLUSPLUS (_MSC_VER == 1900 ? 201103L : _MSVC_LANG ) -# else -# define nsvp_CPLUSPLUS __cplusplus -# endif -#endif - -#define nsvp_CPP98_OR_GREATER ( nsvp_CPLUSPLUS >= 199711L ) -#define nsvp_CPP11_OR_GREATER ( nsvp_CPLUSPLUS >= 201103L ) -#define nsvp_CPP11_OR_GREATER_ ( nsvp_CPLUSPLUS >= 201103L ) -#define nsvp_CPP14_OR_GREATER ( nsvp_CPLUSPLUS >= 201402L ) -#define nsvp_CPP17_OR_GREATER ( nsvp_CPLUSPLUS >= 201703L ) -#define nsvp_CPP20_OR_GREATER ( nsvp_CPLUSPLUS >= 202000L ) - -// half-open range [lo..hi): -#define nsvp_BETWEEN( v, lo, hi ) ( (lo) <= (v) && (v) < (hi) ) - -// 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 nsvp_COMPILER_MSVC_VER (_MSC_VER ) -# define nsvp_COMPILER_MSVC_VERSION (_MSC_VER / 10 - 10 * ( 5 + (_MSC_VER < 1900 ) ) ) -#else -# define nsvp_COMPILER_MSVC_VER 0 -# define nsvp_COMPILER_MSVC_VERSION 0 -#endif - -#define nsvp_COMPILER_VERSION( major, minor, patch ) ( 10 * ( 10 * (major) + (minor) ) + (patch) ) - -#if defined(__clang__) -# define nsvp_COMPILER_CLANG_VERSION nsvp_COMPILER_VERSION(__clang_major__, __clang_minor__, __clang_patchlevel__) -#else -# define nsvp_COMPILER_CLANG_VERSION 0 -#endif - -#if defined(__GNUC__) && !defined(__clang__) -# define nsvp_COMPILER_GNUC_VERSION nsvp_COMPILER_VERSION(__GNUC__, __GNUC_MINOR__, __GNUC_PATCHLEVEL__) -#else -# define nsvp_COMPILER_GNUC_VERSION 0 -#endif - -#if nsvp_BETWEEN( nsvp_COMPILER_MSVC_VER, 1300, 1900 ) -# pragma warning( push ) -# pragma warning( disable: 4345 ) // initialization behavior changed -#endif - -// Presence of language and library features: - -#define nsvp_HAVE( feature ) ( nsvp_HAVE_##feature ) - -#ifdef _HAS_CPP0X -# define nsvp_HAS_CPP0X _HAS_CPP0X -#else -# define nsvp_HAS_CPP0X 0 -#endif - -// Unless defined otherwise below, consider VC14 as C++11 for value_ptr-lite: - -#if nsvp_COMPILER_MSVC_VER >= 1900 -# undef nsvp_CPP11_OR_GREATER -# define nsvp_CPP11_OR_GREATER 1 -#endif - -#define nsvp_CPP11_90 (nsvp_CPP11_OR_GREATER_ || nsvp_COMPILER_MSVC_VER >= 1500) -#define nsvp_CPP11_100 (nsvp_CPP11_OR_GREATER_ || nsvp_COMPILER_MSVC_VER >= 1600) -#define nsvp_CPP11_110 (nsvp_CPP11_OR_GREATER_ || nsvp_COMPILER_MSVC_VER >= 1700) -#define nsvp_CPP11_120 (nsvp_CPP11_OR_GREATER_ || nsvp_COMPILER_MSVC_VER >= 1800) -#define nsvp_CPP11_140 (nsvp_CPP11_OR_GREATER_ || nsvp_COMPILER_MSVC_VER >= 1900) -#define nsvp_CPP11_141 (nsvp_CPP11_OR_GREATER_ || nsvp_COMPILER_MSVC_VER >= 1910) - -#define nsvp_CPP14_000 (nsvp_CPP14_OR_GREATER) -#define nsvp_CPP17_000 (nsvp_CPP17_OR_GREATER) - -// empty bases: - -#if nsvp_COMPILER_MSVC_VER >= 1900 -# define nsvp_DECLSPEC_EMPTY_BASES __declspec(empty_bases) -#else -# define nsvp_DECLSPEC_EMPTY_BASES -#endif - -// Presence of C++11 language features: - -#define nsvp_HAVE_CONSTEXPR_11 nsvp_CPP11_140 -#define nsvp_HAVE_INITIALIZER_LIST nsvp_CPP11_120 -#define nsvp_HAVE_IS_DEFAULT nsvp_CPP11_140 -#define nsvp_HAVE_NOEXCEPT nsvp_CPP11_140 -#define nsvp_HAVE_NULLPTR nsvp_CPP11_100 -#define nsvp_HAVE_REF_QUALIFIER nsvp_CPP11_140 - -// Presence of C++14 language features: - -#define nsvp_HAVE_CONSTEXPR_14 nsvp_CPP14_000 - -// Presence of C++17 language features: - -// no flag - -// Presence of C++ library features: - -#define nsvp_HAVE_TR1_TYPE_TRAITS (!! nsvp_COMPILER_GNUC_VERSION ) -#define nsvp_HAVE_TR1_ADD_POINTER (!! nsvp_COMPILER_GNUC_VERSION ) - -#define nsvp_HAVE_TYPE_TRAITS nsvp_CPP11_90 - -// C++ feature usage: - -#if nsvp_HAVE_CONSTEXPR_11 -# define nsvp_constexpr constexpr -#else -# define nsvp_constexpr /*constexpr*/ -#endif - -#if nsvp_HAVE_CONSTEXPR_14 -# define nsvp_constexpr14 constexpr -#else -# define nsvp_constexpr14 /*constexpr*/ -#endif - -#if nsvp_HAVE_NOEXCEPT -# define nsvp_noexcept noexcept -# define nsvp_noexcept_op noexcept -#else -# define nsvp_noexcept /*noexcept*/ -# define nsvp_noexcept_op(expr) /*noexcept(expr)*/ -#endif - -#if nsvp_HAVE_NULLPTR -# define nsvp_nullptr nullptr -#else -# define nsvp_nullptr NULL -#endif - -#if nsvp_HAVE_REF_QUALIFIER -# define nsvp_ref_qual & -# define nsvp_refref_qual && -#else -# define nsvp_ref_qual /*&*/ -# define nsvp_refref_qual /*&&*/ -#endif - -// additional includes: - -#if ! nsvp_CPP11_OR_GREATER -# include // std::swap() until C++11 -#endif - -#if nsvp_HAVE_INITIALIZER_LIST -# include -#endif - -#if nsvp_HAVE_TYPE_TRAITS -# include -#elif nsvp_HAVE_TR1_TYPE_TRAITS -# include -#endif - -// static assert: - -#if nsvp_CPP11_OR_GREATER -# define nsvp_static_assert( expr, msg ) \ - static_assert( expr, msg ) -#else -# define nsvp_static_assert( expr, msg ) \ - do { typedef int x[(expr) ? 1 : -1]; } while(0) -#endif - -// Method enabling - -#if nsvp_CPP11_OR_GREATER - -#define nsvp_REQUIRES_0(...) \ - template< bool B = (__VA_ARGS__), typename std::enable_if::type = 0 > - -#define nsvp_REQUIRES_T(...) \ - , typename = typename std::enable_if< (__VA_ARGS__), nonstd::vptr::detail::enabler >::type - -#define nsvp_REQUIRES_R(R, ...) \ - typename std::enable_if< (__VA_ARGS__), R>::type - -#define nsvp_REQUIRES_A(...) \ - , typename std::enable_if< (__VA_ARGS__), void*>::type = nullptr - -#endif - -#include -#include -#include -#include - -#if ! nsvp_CONFIG_NO_EXCEPTIONS -# include -#endif - -// -// in_place: code duplicated in any-lite, expected-lite, optional-lite, value-ptr-lite, variant-lite: -// - -#ifndef nonstd_lite_HAVE_IN_PLACE_TYPES -#define nonstd_lite_HAVE_IN_PLACE_TYPES 1 - -// C++17 std::in_place in : - -#if nsvp_CPP17_OR_GREATER - -#include - -namespace nonstd { - -using std::in_place; -using std::in_place_type; -using std::in_place_index; -using std::in_place_t; -using std::in_place_type_t; -using std::in_place_index_t; - -#define nonstd_lite_in_place_t( T) std::in_place_t -#define nonstd_lite_in_place_type_t( T) std::in_place_type_t -#define nonstd_lite_in_place_index_t(K) std::in_place_index_t - -#define nonstd_lite_in_place( T) std::in_place_t{} -#define nonstd_lite_in_place_type( T) std::in_place_type_t{} -#define nonstd_lite_in_place_index(K) std::in_place_index_t{} - -} // namespace nonstd - -#else // nsvp_CPP17_OR_GREATER - -#include - -namespace nonstd { -namespace detail { - -template< class T > -struct in_place_type_tag {}; - -template< std::size_t K > -struct in_place_index_tag {}; - -} // namespace detail - -struct in_place_t {}; - -template< class T > -inline in_place_t in_place( detail::in_place_type_tag = detail::in_place_type_tag() ) -{ - return in_place_t(); -} - -template< std::size_t K > -inline in_place_t in_place( detail::in_place_index_tag = detail::in_place_index_tag() ) -{ - return in_place_t(); -} - -template< class T > -inline in_place_t in_place_type( detail::in_place_type_tag = detail::in_place_type_tag() ) -{ - return in_place_t(); -} - -template< std::size_t K > -inline in_place_t in_place_index( detail::in_place_index_tag = detail::in_place_index_tag() ) -{ - return in_place_t(); -} - -// mimic templated typedef: - -#define nonstd_lite_in_place_t( T) nonstd::in_place_t(&)( nonstd::detail::in_place_type_tag ) -#define nonstd_lite_in_place_type_t( T) nonstd::in_place_t(&)( nonstd::detail::in_place_type_tag ) -#define nonstd_lite_in_place_index_t(K) nonstd::in_place_t(&)( nonstd::detail::in_place_index_tag ) - -#define nonstd_lite_in_place( T) nonstd::in_place_type -#define nonstd_lite_in_place_type( T) nonstd::in_place_type -#define nonstd_lite_in_place_index(K) nonstd::in_place_index - -} // namespace nonstd - -#endif // nsvp_CPP17_OR_GREATER -#endif // nonstd_lite_HAVE_IN_PLACE_TYPES - -// -// value_ptr: -// - -namespace nonstd { namespace vptr { - -#if nsvp_CPP11_OR_GREATER - -namespace std20 { - -// type traits C++20: - -template< typename T > -struct remove_cvref -{ - typedef typename std::remove_cv< typename std::remove_reference::type >::type type; -}; - -} // namespace std20 - -#endif // nsvp_CPP11_OR_GREATER - -namespace detail { - -/*enum*/ class enabler{}; - -#if nsvp_CPP11_OR_GREATER -using std::default_delete; -#else -template< class T > -struct default_delete -{ - default_delete() nsvp_noexcept {}; - - void operator()( T * ptr ) const nsvp_noexcept - { - nsvp_static_assert( sizeof(T) > 0, "default_delete cannot delete incomplete type"); -#if nsvp_CPP11_OR_GREATER - nsvp_static_assert( ! std::is_void::value, "default_delete cannot delete incomplete type"); -#endif - delete ptr; - } -}; -#endif - -template< class T > -struct default_clone -{ -#if nsvp_CPP11_OR_GREATER - default_clone() = default; -#else - default_clone() {}; -#endif - - T * operator()( T const & x ) const - { - nsvp_static_assert( sizeof(T) > 0, "default_clone cannot clone incomplete type"); -#if nsvp_CPP11_OR_GREATER - nsvp_static_assert( ! std::is_void::value, "default_clone cannot clone incomplete type"); -#endif - return new T( x ); - } - -#if nsvp_CPP11_OR_GREATER - T * operator()( T && x ) const - { - return new T( std::move( x ) ); - } - - template< class... Args > - T * operator()( nonstd_lite_in_place_t(T), Args&&... args ) const - { - return new T( std::forward(args)...); - } - - template< class U, class... Args > - T * operator()( nonstd_lite_in_place_t(T), std::initializer_list il, Args&&... args ) const - { - return new T( il, std::forward(args)...); - } -#endif -}; - -template -struct nsvp_DECLSPEC_EMPTY_BASES compressed_ptr : Cloner, Deleter -{ - typedef T element_type; - typedef T * pointer; - - typedef Cloner cloner_type; - typedef Deleter deleter_type; - - // Lifetime: - - ~compressed_ptr() - { - deleter_type()( ptr ); - } - - compressed_ptr() nsvp_noexcept - : ptr( nsvp_nullptr ) - {} - - explicit compressed_ptr( pointer p ) nsvp_noexcept - : ptr( p ) - {} - - compressed_ptr( compressed_ptr const & other ) - : cloner_type ( other ) - , deleter_type( other ) - , ptr( other.ptr ? cloner_type()( *other.ptr ) : nsvp_nullptr ) - {} - -#if nsvp_CPP11_OR_GREATER - compressed_ptr( compressed_ptr && other ) nsvp_noexcept - : cloner_type ( std::move( other ) ) - , deleter_type( std::move( other ) ) - , ptr( std::move( other.ptr ) ) - { - other.ptr = nullptr; - } -#endif - - explicit compressed_ptr( element_type const & value ) - : ptr( cloner_type()( value ) ) - {} - -#if nsvp_CPP11_OR_GREATER - - explicit compressed_ptr( element_type && value ) nsvp_noexcept - : ptr( cloner_type()( std::move( value ) ) ) - {} - - template< class... Args > - explicit compressed_ptr( nonstd_lite_in_place_t(T), Args&&... args ) - : ptr( cloner_type()( nonstd_lite_in_place(T), std::forward(args)...) ) - {} - - template< class U, class... Args > - explicit compressed_ptr( nonstd_lite_in_place_t(T), std::initializer_list il, Args&&... args ) - : ptr( cloner_type()( nonstd_lite_in_place(T), il, std::forward(args)...) ) - {} - -#endif - - compressed_ptr( element_type const & value, cloner_type const & cloner ) - : cloner_type ( cloner ) - , ptr( cloner_type()( value ) ) - {} - -#if nsvp_CPP11_OR_GREATER - compressed_ptr( element_type && value, cloner_type && cloner ) nsvp_noexcept - : cloner_type ( std::move( cloner ) ) - , ptr( cloner_type()( std::move( value ) ) ) - {} -#endif - - compressed_ptr( element_type const & value, cloner_type const & cloner, deleter_type const & deleter ) - : cloner_type ( cloner ) - , deleter_type( deleter ) - , ptr( cloner_type()( value ) ) - {} - -#if nsvp_CPP11_OR_GREATER - compressed_ptr( element_type && value, cloner_type && cloner, deleter_type && deleter ) nsvp_noexcept - : cloner_type ( std::move( cloner ) ) - , deleter_type( std::move( deleter ) ) - , ptr( cloner_type()( std::move( value ) ) ) - {} -#endif - - explicit compressed_ptr( cloner_type const & cloner ) - : cloner_type( cloner ) - , ptr( nsvp_nullptr ) - {} - -#if nsvp_CPP11_OR_GREATER - explicit compressed_ptr( cloner_type && cloner ) nsvp_noexcept - : cloner_type( std::move( cloner ) ) - , ptr( nsvp_nullptr ) - {} -#endif - - explicit compressed_ptr( deleter_type const & deleter ) - : deleter_type( deleter ) - , ptr( nsvp_nullptr ) - {} - -# if nsvp_CPP11_OR_GREATER - explicit compressed_ptr( deleter_type && deleter ) nsvp_noexcept - : deleter_type( std::move( deleter ) ) - , ptr( nsvp_nullptr ) - {} -#endif - - compressed_ptr( cloner_type const & cloner, deleter_type const & deleter ) - : cloner_type ( cloner ) - , deleter_type( deleter ) - , ptr( nsvp_nullptr ) - {} - -#if nsvp_CPP11_OR_GREATER - compressed_ptr( cloner_type && cloner, deleter_type && deleter ) nsvp_noexcept - : cloner_type ( std::move( cloner ) ) - , deleter_type( std::move( deleter ) ) - , ptr( nsvp_nullptr ) - {} -#endif - - // Observers: - - pointer get() const nsvp_noexcept - { - return ptr; - } - - cloner_type & get_cloner() nsvp_noexcept - { - return *this; - } - - deleter_type & get_deleter() nsvp_noexcept - { - return *this; - } - - // Modifiers: - - pointer release() nsvp_noexcept - { - using std::swap; - pointer result = nsvp_nullptr; - swap( result, ptr ); - return result; - } - - void reset( pointer p ) nsvp_noexcept - { - get_deleter()( ptr ); - ptr = p; - } - - void reset( element_type const & v ) - { - reset( get_cloner()( v ) ); - } - -#if nsvp_CPP11_OR_GREATER - void reset( element_type && v ) - { - reset( get_cloner()( std::move( v ) ) ); - } -#endif - - void swap(compressed_ptr& other) nsvp_noexcept - { - using std::swap; - swap(ptr, other.ptr); - } - - pointer ptr; -}; - -} // namespace detail - -#if ! nsvp_CONFIG_NO_EXCEPTIONS - -// value_ptr access error - -class bad_value_access : public std::logic_error -{ -public: - explicit bad_value_access() - : logic_error( "bad value_ptr access" ) {} -}; - -#endif - -// class value_ptr: - -template -< - class T - , class Cloner = detail::default_clone - , class Deleter = detail::default_delete -> -class value_ptr -{ -public: - typedef T element_type; - typedef T * pointer; - typedef T & reference; - typedef T const * const_pointer; - typedef T const & const_reference; - - typedef Cloner cloner_type; - typedef Deleter deleter_type; - - // Lifetime - -#if nsvp_HAVE_IS_DEFAULT - ~value_ptr() = default; -#endif - - value_ptr() nsvp_noexcept - : ptr( cloner_type(), deleter_type() ) - {} - -#if nsvp_HAVE_NULLPTR - explicit value_ptr( std::nullptr_t ) nsvp_noexcept - : ptr( cloner_type(), deleter_type() ) - {} -#endif - - explicit value_ptr( pointer p ) nsvp_noexcept - : ptr( p ) - {} - - value_ptr(value_ptr const& other) - : ptr(other.ptr) - {} - -#if nsvp_CPP11_OR_GREATER - value_ptr( value_ptr && other ) nsvp_noexcept - : ptr( std::move( other.ptr ) ) - {} -#endif - - explicit value_ptr( element_type const & value ) - : ptr( value ) - {} - -#if nsvp_CPP11_OR_GREATER - - explicit value_ptr( element_type && value ) nsvp_noexcept - : ptr( std::move( value ) ) - {} - -#endif // nsvp_CPP11_OR_GREATER - - explicit value_ptr( cloner_type const & cloner ) - : ptr( cloner ) - {} - -#if nsvp_CPP11_OR_GREATER - explicit value_ptr( cloner_type && cloner ) nsvp_noexcept - : ptr( std::move( cloner ) ) - {} -#endif - - explicit value_ptr( deleter_type const & deleter ) - : ptr( deleter ) - {} - -#if nsvp_CPP11_OR_GREATER - explicit value_ptr( deleter_type && deleter ) nsvp_noexcept - : ptr( std::move( deleter ) ) - {} -#endif - -#if nsvp_CPP11_OR_GREATER - template< class V, class ClonerOrDeleter - nsvp_REQUIRES_T( - !std::is_same::type, nonstd_lite_in_place_t(V)>::value ) - > - value_ptr( V && value, ClonerOrDeleter && cloner_or_deleter ) - : ptr( std::forward( value ), std::forward( cloner_or_deleter ) ) - {} -#else - template< class V, class ClonerOrDeleter > - value_ptr( V const & value, ClonerOrDeleter const & cloner_or_deleter ) - : ptr( value, cloner_or_deleter ) - {} -#endif - -#if nsvp_CPP11_OR_GREATER - template< class V, class C, class D - nsvp_REQUIRES_T( - !std::is_same::type, nonstd_lite_in_place_t(V)>::value ) - > - value_ptr( V && value, C && cloner, D && deleter ) - : ptr( std::forward( value ), std::forward( cloner ), std::forward( deleter ) ) - {} -#else - template< class V, class C, class D > - value_ptr( V const & value, C const & cloner, D const & deleter ) - : ptr( value, cloner, deleter ) - {} -#endif - -#if nsvp_HAVE_NULLPTR - value_ptr & operator=( std::nullptr_t ) nsvp_noexcept - { - ptr.reset( nullptr ); - return *this; - } -#endif - - value_ptr & operator=( T const & value ) - { - ptr.reset( value ); - return *this; - } - -#if nsvp_CPP11_OR_GREATER - template< class U - nsvp_REQUIRES_T( - std::is_same< typename std::decay::type, T>::value ) - > - value_ptr & operator=( U && value ) - { - ptr.reset( std::forward( value ) ); - return *this; - } -#endif - - value_ptr & operator=( value_ptr const & rhs ) - { - if ( this == &rhs ) - return *this; - - if ( rhs ) ptr.reset( *rhs ); -#if nsvp_HAVE_NULLPTR - else ptr.reset( nullptr ); -#else - else ptr.reset( pointer(0) ); -#endif - return *this; - } - -#if nsvp_CPP11_OR_GREATER - - value_ptr & operator=( value_ptr && rhs ) nsvp_noexcept - { - if ( this == &rhs ) - return *this; - - swap( rhs ); - - return *this; - } - - template< class... Args > - void emplace( Args&&... args ) - { - ptr.reset( T( std::forward(args)...) ); - } - - template< class U, class... Args > - void emplace( std::initializer_list il, Args&&... args ) - { - ptr.reset( T( il, std::forward(args)...) ); - } - -#endif // nsvp_CPP11_OR_GREATER - - // Observers: - - pointer get() const nsvp_noexcept - { - return ptr.get(); - } - - cloner_type & get_cloner() nsvp_noexcept - { - return ptr.get_cloner(); - } - - deleter_type & get_deleter() nsvp_noexcept - { - return ptr.get_deleter(); - } - - reference operator*() const - { - assert( get() != nsvp_nullptr ); return *get(); - } - - pointer operator->() const nsvp_noexcept - { - assert( get() != nsvp_nullptr ); return get(); - } - -#if nsvp_CPP11_OR_GREATER - explicit operator bool() const nsvp_noexcept - { - return has_value(); - } -#else -private: - typedef void (value_ptr::*safe_bool)() const; - void this_type_does_not_support_comparisons() const {} - -public: - operator safe_bool() const nsvp_noexcept - { - return has_value() ? &value_ptr::this_type_does_not_support_comparisons : 0; - } -#endif - - bool has_value() const nsvp_noexcept - { - return !! get(); - } - - element_type const & value() const - { -#if nsvp_CONFIG_NO_EXCEPTIONS - assert( has_value() ); -#else - if ( ! has_value() ) - { - throw bad_value_access(); - } -#endif - return *get(); - } - - element_type & value() - { -#if nsvp_CONFIG_NO_EXCEPTIONS - assert( has_value() ); -#else - if ( ! has_value() ) - { - throw bad_value_access(); - } -#endif - return *get(); - } - -#if nsvp_CPP11_OR_GREATER - - -#else - - template< class U > - element_type value_or( U const & v ) const - { - return has_value() ? value() : static_cast( v ); - } - -#endif // nsvp_CPP11_OR_GREATER - - // Modifiers: - - pointer release() nsvp_noexcept - { - return ptr.release(); - } - - void reset( pointer p = pointer() ) nsvp_noexcept - { - ptr.reset( p ); - } - - void swap( value_ptr & other ) nsvp_noexcept - { - ptr.swap( other.ptr ); - } - -private: - detail::compressed_ptr ptr; -}; - -// Non-member functions: - -#if nsvp_CPP11_OR_GREATER - -template< class T > -inline value_ptr< typename std::decay::type > make_value( T && v ) -{ - return value_ptr< typename std::decay::type >( std::forward( v ) ); -} - -template< class T, class... Args > -inline value_ptr make_value( Args&&... args ) -{ - return value_ptr( in_place, std::forward(args)...); -} - -template< class T, class U, class... Args > -inline value_ptr make_value( std::initializer_list il, Args&&... args ) -{ - return value_ptr( in_place, il, std::forward(args)...); -} - -#else - -template< typename T > -inline value_ptr make_value( T const & value ) -{ - return value_ptr( value ); -} - -#endif // nsvp_CPP11_OR_GREATER - -// Comparison between value_ptr-s: - -#if nsvp_CONFIG_COMPARE_POINTERS - -// compare pointers: - -template< - class T1, class D1, class C1, - class T2, class D2, class C2 -> -inline bool operator==( - value_ptr const & lhs, - value_ptr const & rhs ) -{ - return lhs.get() == rhs.get(); -} - -template< - class T1, class D1, class C1, - class T2, class D2, class C2 -> -inline bool operator!=( - value_ptr const & lhs, - value_ptr const & rhs ) -{ - return ! ( lhs == rhs ); -} - -template< - class T1, class D1, class C1, - class T2, class D2, class C2 -> -inline bool operator<( - value_ptr const & lhs, - value_ptr const & rhs ) -{ -#if nsvp_CPP11_OR_GREATER - using P1 = typename value_ptr::const_pointer; - using P2 = typename value_ptr::const_pointer; - using CT = typename std::common_type::type; - return std::less()( lhs.get(), rhs.get() ); -#else - return std::less()( lhs.get(), rhs.get() ); -#endif -} - -template< - class T1, class D1, class C1, - class T2, class D2, class C2 -> -inline bool operator<=( - value_ptr const & lhs, - value_ptr const & rhs ) -{ - return !( rhs < lhs ); -} - -template< - class T1, class D1, class C1, - class T2, class D2, class C2 -> -inline bool operator>( - value_ptr const & lhs, - value_ptr const & rhs ) -{ - return rhs < lhs; -} - -template< - class T1, class D1, class C1, - class T2, class D2, class C2 -> -inline bool operator>=( - value_ptr const & lhs, - value_ptr const & rhs ) -{ - return !( lhs < rhs ); -} - -// Comparison with std::nullptr_t: - -#if nsvp_HAVE_NULLPTR - -template< class T, class D, class C > -inline bool operator==( value_ptr const & lhs, std::nullptr_t ) nsvp_noexcept -{ - return ! lhs; -} - -template< class T, class D, class C > -inline bool operator==( std::nullptr_t, value_ptr const & rhs ) nsvp_noexcept -{ - return ! rhs; -} - -template< class T, class D, class C > -inline bool operator!=( value_ptr const & lhs, std::nullptr_t ) nsvp_noexcept -{ - return static_cast( lhs ); -} - -template< class T, class D, class C > -inline bool operator!=( std::nullptr_t, value_ptr const & rhs ) nsvp_noexcept -{ - return static_cast( rhs ); -} - -template< class T, class D, class C > -inline bool operator<( value_ptr const & lhs, std::nullptr_t ) -{ - typedef typename value_ptr::const_pointer P; - return std::less

()( lhs.get(), nullptr ); -} - -template< class T, class D, class C > -inline bool operator<( std::nullptr_t, value_ptr const & rhs ) -{ - typedef typename value_ptr::const_pointer P; - return std::less

()( nullptr, rhs.get() ); -} - -template< class T, class D, class C > -inline bool operator<=( value_ptr const & lhs, std::nullptr_t ) -{ - return !( nullptr < lhs ); -} - -template< class T, class D, class C > -inline bool operator<=( std::nullptr_t, value_ptr const & rhs ) -{ - return !( rhs < nullptr ); -} - -template< class T, class D, class C > -inline bool operator>( value_ptr const & lhs, std::nullptr_t ) -{ - return nullptr < lhs; -} - -template< class T, class D, class C > -inline bool operator>( std::nullptr_t, value_ptr const & rhs ) -{ - return rhs < nullptr; -} - -template< class T, class D, class C > -inline bool operator>=( value_ptr const & lhs, std::nullptr_t ) -{ - return !( lhs < nullptr ); -} - -template< class T, class D, class C > -inline bool operator>=( std::nullptr_t, value_ptr const & rhs ) -{ - return !( nullptr < rhs ); -} - -#endif // nsvp_HAVE_NULLPTR - -#else // nsvp_CONFIG_COMPARE_POINTERS - -// compare content: - -template< - class T1, class D1, class C1, - class T2, class D2, class C2 -> -inline bool operator==( - value_ptr const & lhs, - value_ptr const & rhs ) -{ - return bool(lhs) != bool(rhs) ? false : bool(lhs) == false ? true : *lhs == *rhs; -} - -template< - class T1, class D1, class C1, - class T2, class D2, class C2 -> -inline bool operator!=( - value_ptr const & lhs, - value_ptr const & rhs ) -{ - return ! ( lhs == rhs ); -} - -template< - class T1, class D1, class C1, - class T2, class D2, class C2 -> -inline bool operator<( - value_ptr const & lhs, - value_ptr const & rhs ) -{ -//#if nsvp_CPP11_OR_GREATER -// using E1 = typename value_ptr::element_type; -// using E2 = typename value_ptr::element_type; -// using CT = typename std::common_type::type; -// return std::less()( *lhs, *rhs ); -//#else -// return std::less()( *lhs, *rhs ); -//#endif - - return (!rhs) ? false : (!lhs) ? true : *lhs < *rhs; -} - -template< - class T1, class D1, class C1, - class T2, class D2, class C2 -> -inline bool operator<=( - value_ptr const & lhs, - value_ptr const & rhs ) -{ - return !( rhs < lhs ); -} - -template< - class T1, class D1, class C1, - class T2, class D2, class C2 -> -inline bool operator>( - value_ptr const & lhs, - value_ptr const & rhs ) -{ - return rhs < lhs; -} - -template< - class T1, class D1, class C1, - class T2, class D2, class C2 -> -inline bool operator>=( - value_ptr const & lhs, - value_ptr const & rhs ) -{ - return !( lhs < rhs ); -} - -// compare with value: - -template< class T, class C, class D > -bool operator==( value_ptr const & vp, T const & value ) -{ - return bool(vp) ? *vp == value : false; -} - -template< class T, class C, class D > -bool operator==( T const & value, value_ptr const & vp ) -{ - return bool(vp) ? value == *vp : false; -} - -template< class T, class C, class D > -bool operator!=( value_ptr const & vp, T const & value ) -{ - return bool(vp) ? *vp != value : true; -} - -template< class T, class C, class D > -bool operator!=( T const & value, value_ptr const & vp ) -{ - return bool(vp) ? value != *vp : true; -} - -template< class T, class C, class D > -bool operator<( value_ptr const & vp, T const & value ) -{ - return bool(vp) ? *vp < value : true; -} - -template< class T, class C, class D > -bool operator<( T const & value, value_ptr const & vp ) -{ - return bool(vp) ? value < *vp : false; -} - -template< class T, class C, class D > -bool operator<=( value_ptr const & vp, T const & value ) -{ - return bool(vp) ? *vp <= value : true; -} - -template< class T, class C, class D > -bool operator<=( T const & value, value_ptr const & vp ) -{ - return bool(vp) ? value <= *vp : false; -} - -template< class T, class C, class D > -bool operator>( value_ptr const & vp, T const & value ) -{ - return bool(vp) ? *vp > value : false; -} - -template< class T, class C, class D > -bool operator>( T const & value, value_ptr const & vp ) -{ - return bool(vp) ? value > *vp : true; -} - -template< class T, class C, class D > -bool operator>=( value_ptr const & vp, T const & value ) -{ - return bool(vp) ? *vp >= value : false; -} - -template< class T, class C, class D > -bool operator>=( T const & value, value_ptr const & vp ) -{ - return bool(vp) ? value >= *vp : true; -} - -#endif // nsvp_CONFIG_COMPARE_POINTERS - -// swap: - -template< class T, class D, class C > -inline void swap( - value_ptr & lhs, - value_ptr & rhs ) nsvp_noexcept -{ - lhs.swap( rhs ); -} - -} // namespace vptr - -using namespace vptr; - -} // namespace nonstd - -#if nsvp_CPP11_OR_GREATER - -// Specialize the std::hash algorithm: - -namespace std -{ - -template< class T, class D, class C > -struct hash< nonstd::value_ptr > -{ - typedef nonstd::value_ptr argument_type; - typedef size_t result_type; - - result_type operator()( argument_type const & p ) const nsvp_noexcept - { - return hash()( p.get() ); - } -}; - -} // namespace std - -#endif // nsvp_CPP11_OR_GREATER - -#if nsvp_BETWEEN( nsvp_COMPILER_MSVC_VER, 1300, 1900 ) -# pragma warning( pop ) -#endif - -#endif // NONSTD_VALUE_PTR_LITE_HPP - -#endif diff --git a/src/ast_visitor.h b/src/ast_visitor.h index b29277ee..01b51982 100644 --- a/src/ast_visitor.h +++ b/src/ast_visitor.h @@ -3,7 +3,7 @@ namespace jinja2 { -class RendererBase; +class IRendererBase; class ExpressionEvaluatorBase; class Statement; class ForStatement; @@ -26,6 +26,8 @@ class StatementVisitor; class VisitableStatement { public: + virtual ~VisitableStatement() = default; + virtual void ApplyVisitor(StatementVisitor* visitor) = 0; virtual void ApplyVisitor(StatementVisitor* visitor) const = 0; }; @@ -82,7 +84,7 @@ template using VisitorBase = typename detail::VisitorBase::type; class StatementVisitor : public VisitorBase< - RendererBase, + IRendererBase, Statement, ForStatement, IfStatement, @@ -112,4 +114,4 @@ class StatementVisitor : public VisitorBase< } // namespace jinja2 -#endif \ No newline at end of file +#endif diff --git a/src/error_info.cpp b/src/error_info.cpp index a1f0d23f..b60cc8e2 100644 --- a/src/error_info.cpp +++ b/src/error_info.cpp @@ -73,7 +73,7 @@ struct ValueRenderer template void operator()(const jinja2::RecWrapper& val) const { - this->operator()(const_cast(*val.get())); + this->operator()(const_cast(*val)); } void operator()(const jinja2::GenericMap& /*val*/) const {} diff --git a/src/expression_evaluator.h b/src/expression_evaluator.h index e58ce064..06b1c40f 100644 --- a/src/expression_evaluator.h +++ b/src/expression_evaluator.h @@ -4,6 +4,8 @@ #include "internal_value.h" #include "render_context.h" +#include + #include #include @@ -17,7 +19,7 @@ enum LoopCycleFn = 2 }; -class ExpressionEvaluatorBase +class ExpressionEvaluatorBase : public IComparable { public: virtual ~ExpressionEvaluatorBase() {} @@ -30,22 +32,63 @@ template using ExpressionEvaluatorPtr = std::shared_ptr; using Expression = ExpressionEvaluatorBase; +inline bool operator==(const ExpressionEvaluatorPtr<>& lhs, const ExpressionEvaluatorPtr<>& rhs) +{ + if (lhs && rhs && !lhs->IsEqual(*rhs)) + return false; + if ((lhs && !rhs) || (!lhs && rhs)) + return false; + return true; +} +inline bool operator!=(const ExpressionEvaluatorPtr<>& lhs, const ExpressionEvaluatorPtr<>& rhs) +{ + return !(lhs == rhs); +} + struct CallParams { std::unordered_map kwParams; std::vector posParams; }; +inline bool operator==(const CallParams& lhs, const CallParams& rhs) +{ + if (lhs.kwParams != rhs.kwParams) + return false; + if (lhs.posParams != rhs.posParams) + return false; + return true; +} + +inline bool operator!=(const CallParams& lhs, const CallParams& rhs) +{ + return !(lhs == rhs); +} + struct CallParamsInfo { std::unordered_map> kwParams; std::vector> posParams; }; +inline bool operator==(const CallParamsInfo& lhs, const CallParamsInfo& rhs) +{ + if (lhs.kwParams != rhs.kwParams) + return false; + if (lhs.posParams != rhs.posParams) + return false; + return true; +} + +inline bool operator!=(const CallParamsInfo& lhs, const CallParamsInfo& rhs) +{ + return !(lhs == rhs); +} + struct ArgumentInfo { std::string name; - bool mandatory; + bool mandatory = false; InternalValue defaultVal; ArgumentInfo(std::string argName, bool isMandatory = false, InternalValue def = InternalValue()) @@ -56,6 +99,22 @@ struct ArgumentInfo } }; +inline bool operator==(const ArgumentInfo& lhs, const ArgumentInfo& rhs) +{ + if (lhs.name != rhs.name) + return false; + if (lhs.mandatory != rhs.mandatory) + return false; + if (!(lhs.defaultVal == rhs.defaultVal)) + return false; + return true; +} + +inline bool operator!=(const ArgumentInfo& lhs, const ArgumentInfo& rhs) +{ + return !(lhs == rhs); +} + struct ParsedArgumentsInfo { std::unordered_map> args; @@ -72,6 +131,22 @@ struct ParsedArgumentsInfo } }; +inline bool operator==(const ParsedArgumentsInfo& lhs, const ParsedArgumentsInfo& rhs) +{ + if (lhs.args != rhs.args) + return false; + if (lhs.extraKwArgs != rhs.extraKwArgs) + return false; + if (lhs.extraPosArgs != rhs.extraPosArgs) + return false; + return true; +} + +inline bool operator!=(const ParsedArgumentsInfo& lhs, const ParsedArgumentsInfo& rhs) +{ + return !(lhs == rhs); +} + struct ParsedArguments { std::unordered_map args; @@ -88,6 +163,22 @@ struct ParsedArguments } }; +inline bool operator==(const ParsedArguments& lhs, const ParsedArguments& rhs) +{ + if (lhs.args != rhs.args) + return false; + if (lhs.extraKwArgs != rhs.extraKwArgs) + return false; + if (lhs.extraPosArgs != rhs.extraPosArgs) + return false; + return true; +} + +inline bool operator!=(const ParsedArguments& lhs, const ParsedArguments& rhs) +{ + return !(lhs == rhs); +} + class ExpressionFilter; class IfExpression; @@ -104,6 +195,18 @@ class FullExpressionEvaluator : public ExpressionEvaluatorBase } InternalValue Evaluate(RenderContext& values) override; void Render(OutStream &stream, RenderContext &values) override; + + bool IsEqual(const IComparable& other) const override + { + const auto* eval = dynamic_cast(&other); + if (!eval) + return false; + if (m_expression != eval->m_expression) + return false; + if (m_tester != eval->m_tester) + return false; + return true; + } private: ExpressionEvaluatorPtr m_expression; ExpressionEvaluatorPtr m_tester; @@ -113,10 +216,18 @@ class ValueRefExpression : public Expression { public: ValueRefExpression(std::string valueName) - : m_valueName(valueName) + : m_valueName(std::move(valueName)) { } InternalValue Evaluate(RenderContext& values) override; + + bool IsEqual(const IComparable& other) const override + { + auto* value = dynamic_cast(&other); + if (!value) + return false; + return m_valueName != value->m_valueName; + } private: std::string m_valueName; }; @@ -134,6 +245,18 @@ class SubscriptExpression : public Expression m_subscriptExprs.push_back(value); } + bool IsEqual(const IComparable& other) const override + { + auto* otherPtr = dynamic_cast(&other); + if (!otherPtr) + return false; + if (m_value != otherPtr->m_value) + return false; + if (m_subscriptExprs != otherPtr->m_subscriptExprs) + return false; + return true; + } + private: ExpressionEvaluatorPtr m_value; std::vector> m_subscriptExprs; @@ -148,6 +271,17 @@ class FilteredExpression : public Expression { } InternalValue Evaluate(RenderContext&) override; + bool IsEqual(const IComparable& other) const override + { + auto* otherPtr = dynamic_cast(&other); + if (!otherPtr) + return false; + if (m_expression != otherPtr->m_expression) + return false; + if (m_filter != otherPtr->m_filter) + return false; + return true; + } private: ExpressionEvaluatorPtr m_expression; @@ -164,6 +298,14 @@ class ConstantExpression : public Expression { return m_constant; } + + bool IsEqual(const IComparable& other) const override + { + auto* otherVal = dynamic_cast(&other); + if (!otherVal) + return false; + return m_constant == otherVal->m_constant; + } private: InternalValue m_constant; }; @@ -178,6 +320,13 @@ class TupleCreator : public Expression InternalValue Evaluate(RenderContext&) override; + bool IsEqual(const IComparable& other) const override + { + auto* val = dynamic_cast(&other); + if (!val) + return false; + return m_exprs == val->m_exprs; + } private: std::vector> m_exprs; }; @@ -206,6 +355,13 @@ class DictCreator : public Expression InternalValue Evaluate(RenderContext&) override; + bool IsEqual(const IComparable& other) const override + { + auto* val = dynamic_cast(&other); + if (!val) + return false; + return m_exprs == val->m_exprs; + } private: std::unordered_map> m_exprs; }; @@ -225,6 +381,19 @@ class UnaryExpression : public Expression , m_expr(expr) {} InternalValue Evaluate(RenderContext&) override; + + bool IsEqual(const IComparable& other) const override + { + auto* val = dynamic_cast(&other); + if (!val) + return false; + if (m_oper != val->m_oper) + return false; + if (m_expr != val->m_expr) + return false; + return true; + } + private: Operation m_oper; ExpressionEvaluatorPtr<> m_expr; @@ -235,20 +404,34 @@ class IsExpression : public Expression public: virtual ~IsExpression() {} - struct ITester + struct ITester : IComparable { virtual ~ITester() {} virtual bool Test(const InternalValue& baseVal, RenderContext& context) = 0; }; - - using TesterFactoryFn = std::function(CallParamsInfo params)>; + using TesterPtr = std::shared_ptr; + using TesterFactoryFn = std::function; IsExpression(ExpressionEvaluatorPtr<> value, const std::string& tester, CallParamsInfo params); InternalValue Evaluate(RenderContext& context) override; + bool IsEqual(const IComparable& other) const override + { + auto* val = dynamic_cast(&other); + if (!val) + return false; + if (m_value != val->m_value) + return false; + if (m_tester != val->m_tester) + return false; + if (m_tester && val->m_tester && !m_tester->IsEqual(*val->m_tester)) + return false; + return true; + } + private: ExpressionEvaluatorPtr<> m_value; - std::shared_ptr m_tester; + TesterPtr m_tester; }; class BinaryExpression : public Expression @@ -284,11 +467,29 @@ class BinaryExpression : public Expression BinaryExpression(Operation oper, ExpressionEvaluatorPtr<> leftExpr, ExpressionEvaluatorPtr<> rightExpr); InternalValue Evaluate(RenderContext&) override; + + bool IsEqual(const IComparable& other) const override + { + auto* val = dynamic_cast(&other); + if (!val) + return false; + if (m_oper != val->m_oper) + return false; + if (m_leftExpr != val->m_leftExpr) + return false; + if (m_rightExpr != val->m_rightExpr) + return false; + if (m_inTester && val->m_inTester && !m_inTester->IsEqual(*val->m_inTester)) + return false; + if ((!m_inTester && val->m_inTester) || (m_inTester && !val->m_inTester)) + return false; + return true; + } private: Operation m_oper; ExpressionEvaluatorPtr<> m_leftExpr; ExpressionEvaluatorPtr<> m_rightExpr; - std::shared_ptr m_inTester; + IsExpression::TesterPtr m_inTester; }; @@ -309,6 +510,15 @@ class CallExpression : public Expression auto& GetValueRef() const {return m_valueRef;} auto& GetParams() const {return m_params;} + bool IsEqual(const IComparable& other) const override + { + auto* val = dynamic_cast(&other); + if (!val) + return false; + if (m_valueRef != val->m_valueRef) + return false; + return m_params == val->m_params; + } private: InternalValue CallArbitraryFn(RenderContext &values); InternalValue CallGlobalRange(RenderContext &values); @@ -319,18 +529,18 @@ class CallExpression : public Expression CallParamsInfo m_params; }; -class ExpressionFilter +class ExpressionFilter : public IComparable { public: virtual ~ExpressionFilter() {} - struct IExpressionFilter + struct IExpressionFilter : IComparable { virtual ~IExpressionFilter() {} virtual InternalValue Filter(const InternalValue& baseVal, RenderContext& context) = 0; }; - - using FilterFactoryFn = std::function(CallParamsInfo params)>; + using ExpressionFilterPtr = std::shared_ptr; + using FilterFactoryFn = std::function; ExpressionFilter(const std::string& filterName, CallParamsInfo params); @@ -339,13 +549,28 @@ class ExpressionFilter { m_parentFilter = std::move(parentFilter); } + bool IsEqual(const IComparable& other) const override + { + auto* valuePtr = dynamic_cast(&other); + if (!valuePtr) + return false; + if (m_filter && valuePtr->m_filter && !m_filter->IsEqual(*valuePtr->m_filter)) + return false; + if ((m_filter && !valuePtr->m_filter) || (!m_filter && !valuePtr->m_filter)) + return false; + if (m_parentFilter != valuePtr->m_parentFilter) + return false; + return true; + } + + private: - std::shared_ptr m_filter; + ExpressionFilterPtr m_filter; std::shared_ptr m_parentFilter; }; -class IfExpression +class IfExpression : public IComparable { public: virtual ~IfExpression() {} @@ -364,6 +589,18 @@ class IfExpression m_altValue = std::move(altValue); } + bool IsEqual(const IComparable& other) const override + { + auto* valPtr = dynamic_cast(&other); + if (!valPtr) + return false; + if (m_testExpr != valPtr->m_testExpr) + return false; + if (m_altValue != valPtr->m_altValue) + return false; + return true; + } + private: ExpressionEvaluatorPtr<> m_testExpr; ExpressionEvaluatorPtr<> m_altValue; diff --git a/src/filesystem_handler.cpp b/src/filesystem_handler.cpp index 7ba3fe26..cade4b18 100644 --- a/src/filesystem_handler.cpp +++ b/src/filesystem_handler.cpp @@ -90,6 +90,14 @@ nonstd::optional MemoryFileSystem::GetLas return nonstd::optional(); } +bool MemoryFileSystem::IsEqual(const IComparable& other) const +{ + auto* ptr = dynamic_cast(&other); + if (!ptr) + return false; + return m_filesMap == ptr->m_filesMap; +} + RealFileSystem::RealFileSystem(std::string rootFolder) : m_rootFolder(std::move(rootFolder)) { @@ -144,4 +152,12 @@ CharFileStreamPtr RealFileSystem::OpenByteStream(const std::string& name) const return CharFileStreamPtr(nullptr, [](std::istream*){}); } +bool RealFileSystem::IsEqual(const IComparable& other) const +{ + auto* ptr = dynamic_cast(&other); + if (!ptr) + return false; + return m_rootFolder == ptr->m_rootFolder; +} + } // namespace jinja2 diff --git a/src/filters.h b/src/filters.h index 97c42279..1fdfc465 100644 --- a/src/filters.h +++ b/src/filters.h @@ -27,8 +27,18 @@ class ApplyMacro : public FilterBase public: ApplyMacro(FilterParams params); - InternalValue Filter(const InternalValue& baseVal, RenderContext& context); - + InternalValue Filter(const InternalValue& baseVal, RenderContext& context) override; + bool IsEqual(const IComparable& other) const override + { + auto* value = dynamic_cast(&other); + if (!value) + return false; + if (m_args != value->m_args) + return false; + if (m_mappingParams != value->m_mappingParams) + return false; + return true; + } private: FilterParams m_mappingParams; }; @@ -38,7 +48,17 @@ class Attribute : public FilterBase public: Attribute(FilterParams params); - InternalValue Filter(const InternalValue& baseVal, RenderContext& context); + InternalValue Filter(const InternalValue& baseVal, RenderContext& context) override; + + bool IsEqual(const IComparable& other) const override + { + auto* value = dynamic_cast(&other); + if (!value) + return false; + if (m_args != value->m_args) + return false; + return true; + } }; class Default : public FilterBase @@ -46,7 +66,16 @@ class Default : public FilterBase public: Default(FilterParams params); - InternalValue Filter(const InternalValue& baseVal, RenderContext& context); + InternalValue Filter(const InternalValue& baseVal, RenderContext& context) override; + bool IsEqual(const IComparable& other) const override + { + auto* value = dynamic_cast(&other); + if (!value) + return false; + if (m_args != value->m_args) + return false; + return true; + } }; class DictSort : public FilterBase @@ -54,7 +83,16 @@ class DictSort : public FilterBase public: DictSort(FilterParams params); - InternalValue Filter(const InternalValue& baseVal, RenderContext& context); + InternalValue Filter(const InternalValue& baseVal, RenderContext& context) override; + bool IsEqual(const IComparable& other) const override + { + auto* value = dynamic_cast(&other); + if (!value) + return false; + if (m_args != value->m_args) + return false; + return true; + } }; class GroupBy : public FilterBase @@ -62,7 +100,16 @@ class GroupBy : public FilterBase public: GroupBy(FilterParams params); - InternalValue Filter(const InternalValue& baseVal, RenderContext& context); + InternalValue Filter(const InternalValue& baseVal, RenderContext& context) override; + bool IsEqual(const IComparable& other) const override + { + auto* value = dynamic_cast(&other); + if (!value) + return false; + if (m_args != value->m_args) + return false; + return true; + } }; class Join : public FilterBase @@ -70,7 +117,16 @@ class Join : public FilterBase public: Join(FilterParams params); - InternalValue Filter(const InternalValue& baseVal, RenderContext& context); + InternalValue Filter(const InternalValue& baseVal, RenderContext& context) override; + bool IsEqual(const IComparable& other) const override + { + auto* value = dynamic_cast(&other); + if (!value) + return false; + if (m_args != value->m_args) + return false; + return true; + } }; class Map : public FilterBase @@ -78,8 +134,18 @@ class Map : public FilterBase public: Map(FilterParams params); - InternalValue Filter(const InternalValue& baseVal, RenderContext& context); - + InternalValue Filter(const InternalValue& baseVal, RenderContext& context) override; + bool IsEqual(const IComparable& other) const override + { + auto* value = dynamic_cast(&other); + if (!value) + return false; + if (m_args != value->m_args) + return false; + if (m_mappingParams != value->m_mappingParams) + return false; + return true; + } private: static FilterParams MakeParams(FilterParams); @@ -91,7 +157,16 @@ class PrettyPrint : public FilterBase public: PrettyPrint(FilterParams params); - InternalValue Filter(const InternalValue& baseVal, RenderContext& context); + InternalValue Filter(const InternalValue& baseVal, RenderContext& context) override; + bool IsEqual(const IComparable& other) const override + { + auto* value = dynamic_cast(&other); + if (!value) + return false; + if (m_args != value->m_args) + return false; + return true; + } }; class Random : public FilterBase @@ -99,7 +174,16 @@ class Random : public FilterBase public: Random(FilterParams params); - InternalValue Filter(const InternalValue& baseVal, RenderContext& context); + InternalValue Filter(const InternalValue& baseVal, RenderContext& context) override; + bool IsEqual(const IComparable& other) const override + { + auto* value = dynamic_cast(&other); + if (!value) + return false; + if (m_args != value->m_args) + return false; + return true; + } }; class SequenceAccessor : public FilterBase @@ -121,8 +205,19 @@ class SequenceAccessor : public FilterBase SequenceAccessor(FilterParams params, Mode mode); - InternalValue Filter(const InternalValue& baseVal, RenderContext& context); + InternalValue Filter(const InternalValue& baseVal, RenderContext& context) override; + bool IsEqual(const IComparable& other) const override + { + auto* value = dynamic_cast(&other); + if (!value) + return false; + if (m_args != value->m_args) + return false; + if (m_mode != value->m_mode) + return false; + return true; + } private: Mode m_mode; }; @@ -139,8 +234,19 @@ class Serialize : public FilterBase Serialize(FilterParams params, Mode mode); - InternalValue Filter(const InternalValue& baseVal, RenderContext& context); + InternalValue Filter(const InternalValue& baseVal, RenderContext& context) override; + bool IsEqual(const IComparable& other) const override + { + auto* value = dynamic_cast(&other); + if (!value) + return false; + if (m_args != value->m_args) + return false; + if (m_mode != value->m_mode) + return false; + return true; + } private: Mode m_mode; }; @@ -156,7 +262,18 @@ class Slice : public FilterBase Slice(FilterParams params, Mode mode); - InternalValue Filter(const InternalValue& baseVal, RenderContext& context); + InternalValue Filter(const InternalValue& baseVal, RenderContext& context) override; + bool IsEqual(const IComparable& other) const override + { + auto* value = dynamic_cast(&other); + if (!value) + return false; + if (m_args != value->m_args) + return false; + if (m_mode != value->m_mode) + return false; + return true; + } private: InternalValue Batch(const InternalValue& baseVal, RenderContext& context); @@ -168,7 +285,16 @@ class Sort : public FilterBase public: Sort(FilterParams params); - InternalValue Filter(const InternalValue& baseVal, RenderContext& context); + InternalValue Filter(const InternalValue& baseVal, RenderContext& context) override; + bool IsEqual(const IComparable& other) const override + { + auto* value = dynamic_cast(&other); + if (!value) + return false; + if (m_args != value->m_args) + return false; + return true; + } }; class StringConverter : public FilterBase @@ -196,8 +322,19 @@ class StringConverter : public FilterBase StringConverter(FilterParams params, Mode mode); - InternalValue Filter(const InternalValue& baseVal, RenderContext& context); + InternalValue Filter(const InternalValue& baseVal, RenderContext& context) override; + bool IsEqual(const IComparable& other) const override + { + auto* value = dynamic_cast(&other); + if (!value) + return false; + if (m_args != value->m_args) + return false; + if (m_mode != value->m_mode) + return false; + return true; + } private: Mode m_mode; }; @@ -207,7 +344,20 @@ class StringFormat : public FilterBase public: StringFormat(FilterParams params); - InternalValue Filter(const InternalValue& baseVal, RenderContext& context); + InternalValue Filter(const InternalValue& baseVal, RenderContext& context) override; + + bool IsEqual(const IComparable& other) const override + { + auto* value = dynamic_cast(&other); + if (!value) + return false; + if (m_args != value->m_args) + return false; + if (m_params != value->m_params) + return false; + return true; + } + private: FilterParams m_params; }; @@ -225,8 +375,21 @@ class Tester : public FilterBase Tester(FilterParams params, Mode mode); - InternalValue Filter(const InternalValue& baseVal, RenderContext& context); + InternalValue Filter(const InternalValue& baseVal, RenderContext& context) override; + bool IsEqual(const IComparable& other) const override + { + auto* value = dynamic_cast(&other); + if (!value) + return false; + if (m_args != value->m_args) + return false; + if (m_mode != value->m_mode) + return false; + if (m_testingParams != value->m_testingParams) + return false; + return true; + } private: Mode m_mode; FilterParams m_testingParams; @@ -247,8 +410,17 @@ class ValueConverter : public FilterBase ValueConverter(FilterParams params, Mode mode); - InternalValue Filter(const InternalValue& baseVal, RenderContext& context); + InternalValue Filter(const InternalValue& baseVal, RenderContext& context) override; + bool IsEqual(const IComparable& other) const override + { + auto* value = dynamic_cast(&other); + if (!value) + return false; + if (m_args != value->m_args) + return false; + return m_mode == value->m_mode; + } private: Mode m_mode; }; @@ -258,7 +430,16 @@ class XmlAttrFilter : public FilterBase public: explicit XmlAttrFilter(FilterParams params); - InternalValue Filter(const InternalValue& baseVal, RenderContext& context); + InternalValue Filter(const InternalValue& baseVal, RenderContext& context) override; + bool IsEqual(const IComparable& other) const override + { + auto* value = dynamic_cast(&other); + if (!value) + return false; + if (m_args != value->m_args) + return false; + return true; + } }; class UserDefinedFilter : public FilterBase @@ -266,7 +447,21 @@ class UserDefinedFilter : public FilterBase public: UserDefinedFilter(std::string filterName, FilterParams params); - InternalValue Filter(const InternalValue& baseVal, RenderContext& context); + InternalValue Filter(const InternalValue& baseVal, RenderContext& context) override; + + bool IsEqual(const IComparable& other) const override + { + auto* value = dynamic_cast(&other); + if (!value) + return false; + if (m_args != value->m_args) + return false; + if (m_filterName != value->m_filterName) + return false; + if (m_callParams != m_callParams) + return false; + return true; + } private: std::string m_filterName; diff --git a/src/function_base.h b/src/function_base.h index 5c59c4c4..578545b0 100644 --- a/src/function_base.h +++ b/src/function_base.h @@ -9,6 +9,14 @@ namespace jinja2 class FunctionBase { public: + bool operator==(const FunctionBase& other) const + { + return m_args == other.m_args; + } + bool operator!=(const FunctionBase& other) const + { + return !(*this == other); + } protected: bool ParseParams(const std::initializer_list& argsInfo, const CallParamsInfo& params); InternalValue GetArgumentValue(const std::string& argName, RenderContext& context, InternalValue defVal = InternalValue()); @@ -17,6 +25,11 @@ class FunctionBase ParsedArgumentsInfo m_args; }; +//bool operator==(const FunctionBase& lhs, const FunctionBase& rhs) +//{ +// return +//} + inline bool FunctionBase::ParseParams(const std::initializer_list& argsInfo, const CallParamsInfo& params) { bool result = true; diff --git a/src/generic_adapters.h b/src/generic_adapters.h index bd3191be..a6493d2a 100644 --- a/src/generic_adapters.h +++ b/src/generic_adapters.h @@ -34,23 +34,39 @@ class IndexedEnumeratorImpl : public Base return m_curItem < m_maxItems; } + bool IsEqual(const IComparable& other) const override + { + auto* val = dynamic_cast(&other); + if (!val) + return false; + if (m_list && val->m_list && !m_list->IsEqual(*val->m_list)) + return false; + if ((m_list && !val->m_list) || (!m_list && val->m_list)) + return false; + if (m_curItem != val->m_curItem) + return false; + if (m_maxItems != val->m_maxItems) + return false; + return true; + } + protected: constexpr static auto m_invalidIndex = std::numeric_limits::max(); - const List* m_list; + const List* m_list{}; size_t m_curItem = m_invalidIndex; - size_t m_maxItems; + size_t m_maxItems{}; }; template -class IndexedListItemAccessorImpl : public ListItemAccessor, public IndexBasedAccessor +class IndexedListItemAccessorImpl : public IListItemAccessor, public IIndexBasedAccessor { public: using ThisType = IndexedListItemAccessorImpl; - class Enumerator : public IndexedEnumeratorImpl + class Enumerator : public IndexedEnumeratorImpl { public: - using BaseClass = IndexedEnumeratorImpl; + using BaseClass = IndexedEnumeratorImpl; #if defined(_MSC_VER) using IndexedEnumeratorImpl::IndexedEnumeratorImpl; #else @@ -68,7 +84,7 @@ class IndexedListItemAccessorImpl : public ListItemAccessor, public IndexBasedAc ListEnumeratorPtr Clone() const override { auto result = MakeEnumerator(this->m_list); - auto base = static_cast(result.get()); + auto base = static_cast(&(*result)); base->m_curItem = this->m_curItem; return result; } @@ -76,7 +92,7 @@ class IndexedListItemAccessorImpl : public ListItemAccessor, public IndexBasedAc ListEnumeratorPtr Move() override { auto result = MakeEnumerator(this->m_list); - auto base = static_cast(result.get()); + auto base = static_cast(&(*result)); base->m_curItem = this->m_curItem; this->m_list = nullptr; this->m_curItem = this->m_invalidIndex; @@ -95,13 +111,24 @@ class IndexedListItemAccessorImpl : public ListItemAccessor, public IndexBasedAc return static_cast(this)->GetItemsCountImpl(); } - const IndexBasedAccessor* GetIndexer() const override + const IIndexBasedAccessor* GetIndexer() const override { return this; } ListEnumeratorPtr CreateEnumerator() const override; + bool IsEqual(const IComparable& other) const override + { + auto* val = dynamic_cast(&other); + if (!val) + return false; + auto enumerator = CreateEnumerator(); + auto otherEnum = val->CreateEnumerator(); + if (enumerator && otherEnum && !enumerator->IsEqual(*otherEnum)) + return false; + return true; + } }; template @@ -156,7 +183,7 @@ class IndexedListAccessorImpl : public IListAccessor, public IndexedListItemAcce }; template -class MapItemAccessorImpl : public MapItemAccessor +class MapItemAccessorImpl : public IMapItemAccessor { public: Value GetValueByName(const std::string& name) const diff --git a/src/generic_list.cpp b/src/generic_list.cpp new file mode 100644 index 00000000..883ef361 --- /dev/null +++ b/src/generic_list.cpp @@ -0,0 +1,38 @@ +#include +#include + +namespace jinja2 { + +detail::GenericListIterator GenericList::begin() const +{ + return m_accessor && m_accessor() ? detail::GenericListIterator(m_accessor()->CreateEnumerator()) : detail::GenericListIterator(); +} + +detail::GenericListIterator GenericList::end() const +{ + return detail::GenericListIterator(); +} + +auto GenericList::cbegin() const {return begin();} +auto GenericList::cend() const {return end();} + +bool GenericList::IsEqual(const GenericList& rhs) const +{ + if (IsValid() && rhs.IsValid() && !GetAccessor()->IsEqual(*rhs.GetAccessor())) + return false; + if ((IsValid() && !rhs.IsValid()) || (!IsValid() && rhs.IsValid())) + return false; + return true; +} + +bool operator==(const GenericList& lhs, const GenericList& rhs) +{ + return lhs.IsEqual(rhs); +} + +bool operator!=(const GenericList& lhs, const GenericList& rhs) +{ + return !(lhs == rhs); +} + +} // namespace jinja2 diff --git a/src/internal_value.cpp b/src/internal_value.cpp index 373ef191..3f64eeff 100644 --- a/src/internal_value.cpp +++ b/src/internal_value.cpp @@ -8,6 +8,109 @@ namespace jinja2 { +std::atomic_uint64_t UserCallable::m_gen{}; + +bool Value::IsEqual(const Value& rhs) const +{ + return this->m_data == rhs.m_data; +} + +bool operator==(const Value& lhs, const Value& rhs) +{ + return lhs.IsEqual(rhs); +} + +bool operator!=(const Value& lhs, const Value& rhs) +{ + return !(lhs == rhs); +} + +bool operator==(const GenericMap& lhs, const GenericMap& rhs) +{ + auto* lhsAccessor = lhs.GetAccessor(); + auto* rhsAccessor = rhs.GetAccessor(); + return lhsAccessor && rhsAccessor && lhsAccessor->IsEqual(*rhsAccessor); +} + +bool operator!=(const GenericMap& lhs, const GenericMap& rhs) +{ + return !(lhs == rhs); +} + +bool operator==(const UserCallable& lhs, const UserCallable& rhs) +{ + // TODO: rework + return lhs.IsEqual(rhs); +} + +bool operator!=(const UserCallable& lhs, const UserCallable& rhs) +{ + return !(lhs == rhs); +} + +bool operator==(const types::ValuePtr& lhs, const types::ValuePtr& rhs) +{ + if (lhs && rhs) + return *lhs == *rhs; + if ((lhs && !rhs) || (!lhs && rhs)) + return false; + return true; +} + +bool operator!=(const types::ValuePtr& lhs, const types::ValuePtr& rhs) +{ + return !(lhs == rhs); +} + +bool operator==(const types::ValuePtr& lhs, const types::ValuePtr& rhs) +{ + if (lhs && rhs) + return *lhs == *rhs; + if ((lhs && !rhs) || (!lhs && rhs)) + return false; + return true; +} + +bool operator!=(const types::ValuePtr& lhs, const types::ValuePtr& rhs) +{ + return !(lhs == rhs); +} + +bool operator==(const types::ValuePtr& lhs, const types::ValuePtr& rhs) +{ + if (lhs && rhs) + return *lhs == *rhs; + if ((lhs && !rhs) || (!lhs && rhs)) + return false; + return true; +} + +bool operator!=(const types::ValuePtr& lhs, const types::ValuePtr& rhs) +{ + return !(lhs == rhs); +} + +bool operator==(const types::ValuePtr>& lhs, const types::ValuePtr>& rhs) +{ + if (lhs && rhs) + return *lhs == *rhs; + if ((lhs && !rhs) || (!lhs && rhs)) + return false; + return true; +} + +bool operator!=(const types::ValuePtr>& lhs, const types::ValuePtr>& rhs) +{ + return !(lhs == rhs); +} + +bool InternalValue::IsEqual(const InternalValue &other) const +{ + if (m_data != other.m_data) + return false; + return m_parentData == other.m_parentData; +} + InternalValue Value2IntValue(const Value& val); InternalValue Value2IntValue(Value&& val); @@ -151,7 +254,7 @@ struct ListConverter : public visitors::BaseVisitor } result_t operator()(const ListAdapter& list) const { return list; } - result_t operator()(const MapAdapter& map) const + result_t operator()(const MapAdapter& map) const { if (strictConvertion) return result_t(); @@ -160,7 +263,7 @@ struct ListConverter : public visitors::BaseVisitor for (auto& k : map.GetKeys()) list.push_back(TargetString(k)); - return ListAdapter::CreateAdapter(std::move(list)); + return ListAdapter::CreateAdapter(std::move(list)); } template @@ -173,7 +276,7 @@ struct ListConverter : public visitors::BaseVisitor template result_t operator()(const nonstd::basic_string_view& str) const { - return strictConvertion ? result_t() : result_t(ListAdapter::CreateAdapter(str.size(), [str](size_t idx) { + return strictConvertion ? result_t() : result_t(ListAdapter::CreateAdapter(str.size(), [str](size_t idx) { return TargetString(std::basic_string(str[idx], 1)); })); } }; @@ -218,9 +321,20 @@ class ByRef const T& Get() const { return *m_val; } T& Get() { return *const_cast(m_val); } bool ShouldExtendLifetime() const { return false; } - + bool operator==(const ByRef& other) const + { + if (m_val && other.m_val && m_val != other.m_val) + return false; + if ((m_val && !other.m_val) || (!m_val && other.m_val)) + return false; + return true; + } + bool operator!=(const ByRef& other) const + { + return !(*this == other); + } private: - const T* m_val; + const T* m_val{}; }; template @@ -236,7 +350,14 @@ class ByVal const T& Get() const { return m_val; } T& Get() { return m_val; } bool ShouldExtendLifetime() const { return false; } - + bool operator==(const ByVal& other) const + { + return m_val == other.m_val; + } + bool operator!=(const ByVal& other) const + { + return !(*this == other); + } private: T m_val; }; @@ -255,6 +376,14 @@ class BySharedVal T& Get() { return *m_val; } bool ShouldExtendLifetime() const { return true; } + bool operator==(const BySharedVal& other) const + { + return m_val == other.m_val; + } + bool operator!=(const BySharedVal& other) const + { + return !(*this == other); + } private: std::shared_ptr m_val; }; @@ -282,6 +411,17 @@ class GenericListAdapter : public IListAccessor InternalValue GetCurrent() const override { return !m_enum ? InternalValue() : Value2IntValue(m_enum->GetCurrent()); } IListAccessorEnumerator* Clone() const override { return !m_enum ? new Enumerator(MakeEmptyListEnumeratorPtr()) : new Enumerator(m_enum->Clone()); } IListAccessorEnumerator* Transfer() override { return new Enumerator(std::move(m_enum)); } + bool IsEqual(const IComparable& other) const override + { + auto* val = dynamic_cast(&other); + if (!val) + return false; + if (m_enum && val->m_enum && !m_enum->IsEqual(*val->m_enum)) + return false; + if ((m_enum && !val->m_enum) || (!m_enum && val->m_enum)) + return false; + return true; + } }; template @@ -293,7 +433,7 @@ class GenericListAdapter : public IListAccessor nonstd::optional GetSize() const override { return m_values.Get().GetSize(); } nonstd::optional GetItem(int64_t idx) const override { - const ListItemAccessor* accessor = m_values.Get().GetAccessor(); + const IListItemAccessor* accessor = m_values.Get().GetAccessor(); auto indexer = accessor->GetIndexer(); if (!indexer) return nonstd::optional(); @@ -304,7 +444,7 @@ class GenericListAdapter : public IListAccessor bool ShouldExtendLifetime() const override { return m_values.ShouldExtendLifetime(); } ListAccessorEnumeratorPtr CreateListAccessorEnumerator() const override { - const ListItemAccessor* accessor = m_values.Get().GetAccessor(); + const IListItemAccessor* accessor = m_values.Get().GetAccessor(); if (!accessor) return ListAccessorEnumeratorPtr(new Enumerator(MakeEmptyListEnumeratorPtr())); return ListAccessorEnumeratorPtr(new Enumerator(m_values.Get().GetAccessor()->CreateEnumerator())); @@ -312,7 +452,7 @@ class GenericListAdapter : public IListAccessor GenericList CreateGenericList() const override { // return m_values.Get(); - return GenericList([list = m_values]() -> const ListItemAccessor* { return list.Get().GetAccessor(); }); + return GenericList([list = m_values]() -> const IListItemAccessor* { return list.Get().GetAccessor(); }); } private: @@ -339,7 +479,7 @@ class ValuesListAdapter : public IndexedListAccessorImpl const ListItemAccessor* { return &list; }); + return GenericList([list = *this]() -> const IListItemAccessor* { return &list; }); } private: @@ -361,7 +501,7 @@ ListAdapter ListAdapter::CreateAdapter(InternalValueList&& values) bool ShouldExtendLifetime() const override { return false; } GenericList CreateGenericList() const override { - return GenericList([adapter = *this]() -> const ListItemAccessor* { return &adapter; }); + return GenericList([adapter = *this]() -> const IListItemAccessor* { return &adapter; }); } private: @@ -404,6 +544,8 @@ ListAdapter ListAdapter::CreateAdapter(std::function(&other); + if (!val) + return false; + if (m_isFinished != val->m_isFinished) + return false; + if (m_current != val->m_current) + return false; + // TODO: compare fn? + if (m_fn != val->m_fn) + return false; + return true; + } + protected: - const GenFn* m_fn; + const GenFn* m_fn{}; InternalValue m_current; bool m_isFinished = false; }; @@ -482,7 +639,7 @@ ListAdapter ListAdapter::CreateAdapter(size_t listSize, std::function const ListItemAccessor* { return &adapter; }); + return GenericList([adapter = *this]() -> const IListItemAccessor* { return &adapter; }); } private: @@ -584,9 +741,15 @@ class InternalValueMapAdapter : public MapAccessorImpl const MapItemAccessor* { return &accessor; }); + return GenericMap([accessor = *this]() -> const IMapItemAccessor* { return &accessor; }); + } + bool IsEqual(const IComparable& other) const override + { + auto* val = dynamic_cast(&other); + if (!val) + return false; + return m_values == val->m_values; } - private: Holder m_values; }; @@ -633,9 +796,15 @@ class GenericMapAdapter : public MapAccessorImpl> bool ShouldExtendLifetime() const override { return m_values.ShouldExtendLifetime(); } GenericMap CreateGenericMap() const override { - return GenericMap([accessor = *this]() -> const MapItemAccessor* { return accessor.m_values.Get().GetAccessor(); }); + return GenericMap([accessor = *this]() -> const IMapItemAccessor* { return accessor.m_values.Get().GetAccessor(); }); + } + bool IsEqual(const IComparable& other) const override + { + auto* val = dynamic_cast(&other); + if (!val) + return false; + return m_values == val->m_values; } - private: Holder m_values; }; @@ -673,9 +842,15 @@ class ValuesMapAdapter : public MapAccessorImpl> bool ShouldExtendLifetime() const override { return m_values.ShouldExtendLifetime(); } GenericMap CreateGenericMap() const override { - return GenericMap([accessor = *this]() -> const MapItemAccessor* { return &accessor; }); + return GenericMap([accessor = *this]() -> const IMapItemAccessor* { return &accessor; }); + } + bool IsEqual(const IComparable& other) const override + { + auto* val = dynamic_cast(&other); + if (!val) + return false; + return m_values == val->m_values; } - private: Holder m_values; }; @@ -741,18 +916,18 @@ struct OutputValueConvertor result_t operator()(const KeyValuePair& pair) const { return ValuesMap{ { "key", Value(pair.key) }, { "value", IntValue2Value(pair.value) } }; } result_t operator()(const Callable&) const { return result_t(); } result_t operator()(const UserCallable&) const { return result_t(); } - result_t operator()(const std::shared_ptr&) const { return result_t(); } + result_t operator()(const std::shared_ptr&) const { return result_t(); } template result_t operator()(const RecWrapper& val) const { - return this->operator()(const_cast(*val.get())); + return this->operator()(const_cast(*val)); } template result_t operator()(RecWrapper& val) const { - return this->operator()(*val.get()); + return this->operator()(*val); } template @@ -777,7 +952,7 @@ Value IntValue2Value(const InternalValue& val) return Apply(val); } -class ContextMapper : public MapItemAccessor +class ContextMapper : public IMapItemAccessor { public: explicit ContextMapper(RenderContext* context) @@ -800,6 +975,20 @@ class ContextMapper : public MapItemAccessor } std::vector GetKeys() const override { return std::vector(); } + bool IsEqual(const IComparable& other) const override + { + auto* val = dynamic_cast(&other); + if (!val) + return false; + if (m_context && val->m_context && !m_context->IsEqual(*val->m_context)) + { + return false; + } + if ((m_context && !val->m_context) || (!m_context && val->m_context)) + return false; + return true; + } + private: RenderContext* m_context; }; @@ -829,15 +1018,15 @@ UserCallableParams PrepareUserCallableParams(const CallParams& params, RenderCon } ValuesMap extraKwArgs; - for (auto p : args.extraKwArgs) + for (auto& p : args.extraKwArgs) extraKwArgs[p.first] = IntValue2Value(p.second); result.extraKwArgs = Value(std::move(extraKwArgs)); ValuesList extraPosArgs; - for (auto p : args.extraPosArgs) + for (auto& p : args.extraPosArgs) extraPosArgs.push_back(IntValue2Value(p)); result.extraPosArgs = Value(std::move(extraPosArgs)); - result.context = GenericMap([accessor = ContextMapper(&context)]() -> const MapItemAccessor* { return &accessor; }); + result.context = GenericMap([accessor = ContextMapper(&context)]() -> const IMapItemAccessor* { return &accessor; }); return result; } diff --git a/src/internal_value.h b/src/internal_value.h index 845f60e9..b674db1c 100644 --- a/src/internal_value.h +++ b/src/internal_value.h @@ -2,6 +2,7 @@ #define JINJA2CPP_SRC_INTERNAL_VALUE_H #include +//#include #include #include @@ -106,7 +107,7 @@ class OutStream; class Callable; struct CallParams; struct KeyValuePair; -class RendererBase; +class IRendererBase; class InternalValue; using InternalValueData = nonstd::variant< @@ -122,7 +123,8 @@ using InternalValueData = nonstd::variant< MapAdapter, RecursiveWrapper, RecursiveWrapper, - std::shared_ptr>; + std::shared_ptr>; + using InternalValueRef = ReferenceWrapper; using InternalValueList = std::vector; @@ -178,7 +180,7 @@ struct IsRecursive : std::true_type {}; template<> struct IsRecursive : std::true_type {}; -struct IListAccessorEnumerator +struct IListAccessorEnumerator : virtual IComparable { virtual ~IListAccessorEnumerator() {} @@ -189,7 +191,7 @@ struct IListAccessorEnumerator virtual IListAccessorEnumerator* Clone() const = 0; virtual IListAccessorEnumerator* Transfer() = 0; - +/* struct Cloner { Cloner() = default; @@ -204,9 +206,10 @@ struct IListAccessorEnumerator return x.Transfer(); } }; + */ }; -using ListAccessorEnumeratorPtr = nonstd::value_ptr; +using ListAccessorEnumeratorPtr = types::ValuePtr; struct IListAccessor { @@ -224,6 +227,7 @@ using ListAccessorProvider = std::function; struct IMapAccessor { + virtual ~IMapAccessor() = default; virtual size_t GetSize() const = 0; virtual bool HasValue(const std::string& name) const = 0; virtual InternalValue GetItem(const std::string& name) const = 0; @@ -375,6 +379,9 @@ class InternalValue auto& GetData() const {return m_data;} auto& GetData() {return m_data;} + auto& GetParentData() {return m_parentData;} + auto& GetParentData() const {return m_parentData;} + void SetParentData(const InternalValue& val) { m_parentData = val.GetData(); @@ -403,11 +410,22 @@ class InternalValue bool IsEmpty() const {return m_data.index() == 0;} + bool IsEqual(const InternalValue& other) const; + private: InternalValueData m_data; InternalValueData m_parentData; }; +inline bool operator==(const InternalValue& lhs, const InternalValue& rhs) +{ + return lhs.IsEqual(rhs); +} +inline bool operator!=(const InternalValue& lhs, const InternalValue& rhs) +{ + return !(lhs == rhs); +} + class ListAdapter::Iterator : public boost::iterator_facade< Iterator, @@ -440,8 +458,12 @@ class ListAdapter::Iterator if (!other.m_iterator) return this->m_isFinished; - - return this->m_iterator.get() == other.m_iterator.get() && this->m_currentIndex == other.m_currentIndex; +// return true; + //const InternalValue& lhs = *(this->m_iterator); + //const InternalValue& rhs = *(other.m_iterator); + //return lhs == rhs; + return this->m_iterator->GetCurrent() == other.m_iterator->GetCurrent() && this->m_currentIndex == other.m_currentIndex; + ///return *(this->m_iterator) == *(other.m_iterator) && this->m_currentIndex == other.m_currentIndex; } const InternalValue& dereference() const diff --git a/src/render_context.h b/src/render_context.h index f62204a3..0239efda 100644 --- a/src/render_context.h +++ b/src/render_context.h @@ -2,9 +2,10 @@ #define JINJA2CPP_SRC_RENDER_CONTEXT_H #include "internal_value.h" +#include +#include #include -#include #include #include @@ -14,8 +15,9 @@ namespace jinja2 template class TemplateImpl; -struct IRendererCallback +struct IRendererCallback : IComparable { + virtual ~IRendererCallback() {} virtual TargetString GetAsTargetString(const InternalValue& val) = 0; virtual OutStream GetStreamOnString(TargetString& str) = 0; virtual nonstd::variantm_currentScope, other.m_currentScope)) + return false; + if (!IsEqual(m_externalScope, other.m_externalScope)) + return false; + if (!IsEqual(m_globalScope, other.m_globalScope)) + return false; + if (!IsEqual(m_boundScope, other.m_boundScope)) + return false; + if (m_emptyScope != other.m_emptyScope) + return false; + if (m_scopes != other.m_scopes) + return false; + return true; + } + +private: + + bool IsEqual(const IRendererCallback* lhs, const IRendererCallback* rhs) const + { + if (lhs && rhs) + return lhs->IsEqual(*rhs); + if ((!lhs && rhs) || (lhs && !rhs)) + return false; + return true; + } + + bool IsEqual(const InternalValueMap* lhs, const InternalValueMap* rhs) const + { + if (lhs && rhs) + return *lhs == *rhs; + if ((!lhs && rhs) || (lhs && !rhs)) + return false; + return true; + } + private: - InternalValueMap* m_currentScope; - const InternalValueMap* m_externalScope; - const InternalValueMap* m_globalScope; + IRendererCallback* m_rendererCallback{}; + InternalValueMap* m_currentScope{}; + const InternalValueMap* m_externalScope{}; + const InternalValueMap* m_globalScope{}; + const InternalValueMap* m_boundScope{}; InternalValueMap m_emptyScope; std::deque m_scopes; - IRendererCallback* m_rendererCallback; - const InternalValueMap* m_boundScope = nullptr; }; } // namespace jinja2 diff --git a/src/renderer.h b/src/renderer.h index a94922bc..9c08d237 100644 --- a/src/renderer.h +++ b/src/renderer.h @@ -1,13 +1,15 @@ #ifndef JINJA2CPP_SRC_RENDERER_H #define JINJA2CPP_SRC_RENDERER_H -#include "jinja2cpp/value.h" #include "out_stream.h" #include "lexertk.h" #include "expression_evaluator.h" #include "render_context.h" #include "ast_visitor.h" +#include +#include + #include #include #include @@ -15,18 +17,32 @@ namespace jinja2 { -class RendererBase +class IRendererBase : public virtual IComparable { public: - virtual ~RendererBase() = default; + virtual ~IRendererBase() = default; virtual void Render(OutStream& os, RenderContext& values) = 0; }; -class VisitableRendererBase : public RendererBase, public VisitableStatement -{ +class VisitableRendererBase : public IRendererBase, public VisitableStatement +{ }; -using RendererPtr = std::shared_ptr; +using RendererPtr = std::shared_ptr; + +inline bool operator==(const RendererPtr& lhs, const RendererPtr& rhs) +{ + if (lhs && rhs && !lhs->IsEqual(*rhs)) + return false; + if ((lhs && !rhs) || (!lhs && rhs)) + return false; + return true; +} + +inline bool operator!=(const RendererPtr& lhs, const RendererPtr& rhs) +{ + return !(lhs == rhs); +} class ComposedRenderer : public VisitableRendererBase { @@ -43,6 +59,14 @@ class ComposedRenderer : public VisitableRendererBase r->Render(os, values); } + bool IsEqual(const IComparable& other) const override + { + auto* val = dynamic_cast(&other); + if (!val) + return false; + return m_renderers == val->m_renderers; + } + private: std::vector m_renderers; }; @@ -51,7 +75,7 @@ class RawTextRenderer : public VisitableRendererBase { public: VISITABLE_STATEMENT(); - + RawTextRenderer(const void* ptr, size_t len) : m_ptr(ptr) , m_length(len) @@ -62,9 +86,19 @@ class RawTextRenderer : public VisitableRendererBase { os.WriteBuffer(m_ptr, m_length); } + + bool IsEqual(const IComparable& other) const override + { + auto* val = dynamic_cast(&other); + if (!val) + return false; + if (m_ptr != val->m_ptr) + return false; + return m_length == val->m_length; + } private: - const void* m_ptr; - size_t m_length; + const void* m_ptr{}; + size_t m_length{}; }; class ExpressionRenderer : public VisitableRendererBase @@ -81,6 +115,14 @@ class ExpressionRenderer : public VisitableRendererBase { m_expression->Render(os, values); } + + bool IsEqual(const IComparable& other) const override + { + auto* val = dynamic_cast(&other); + if (!val) + return false; + return m_expression == val->m_expression; + } private: ExpressionEvaluatorPtr<> m_expression; }; diff --git a/src/robin_hood.h b/src/robin_hood.h index 15cdea60..511a308d 100644 --- a/src/robin_hood.h +++ b/src/robin_hood.h @@ -5,13 +5,12 @@ // /_/ \____/ /_.___/ /_/ /_/ /_/ ________/_/ /_/ \____/ \____/ \__,_/ // _/_____/ // -// robin_hood::unordered_map for C++11 -// version 3.3.2 +// Fast & memory efficient hashtable based on robin hood hashing for C++11/14/17/20 // https://github.com/martinus/robin-hood-hashing // // Licensed under the MIT License . // SPDX-License-Identifier: MIT -// Copyright (c) 2018-2019 Martin Ankerl +// Copyright (c) 2018-2021 Martin Ankerl // // Permission is hereby granted, free of charge, to any person obtaining a copy // of this software and associated documentation files (the "Software"), to deal @@ -35,23 +34,29 @@ #define ROBIN_HOOD_H_INCLUDED // see https://semver.org/ -#define ROBIN_HOOD_VERSION_MAJOR 3 // for incompatible API changes -#define ROBIN_HOOD_VERSION_MINOR 3 // for adding functionality in a backwards-compatible manner -#define ROBIN_HOOD_VERSION_PATCH 2 // for backwards-compatible bug fixes +#define ROBIN_HOOD_VERSION_MAJOR 3 // for incompatible API changes +#define ROBIN_HOOD_VERSION_MINOR 11 // for adding functionality in a backwards-compatible manner +#define ROBIN_HOOD_VERSION_PATCH 3 // for backwards-compatible bug fixes #include #include #include #include +#include +#include // only to support hash of smart pointers #include #include #include #include +#if __cplusplus >= 201703L +# include +#endif // #define ROBIN_HOOD_LOG_ENABLED #ifdef ROBIN_HOOD_LOG_ENABLED # include -# define ROBIN_HOOD_LOG(x) std::cout << __FUNCTION__ << "@" << __LINE__ << ": " << x << std::endl +# define ROBIN_HOOD_LOG(...) \ + std::cout << __FUNCTION__ << "@" << __LINE__ << ": " << __VA_ARGS__ << std::endl; #else # define ROBIN_HOOD_LOG(x) #endif @@ -59,12 +64,34 @@ // #define ROBIN_HOOD_TRACE_ENABLED #ifdef ROBIN_HOOD_TRACE_ENABLED # include -# define ROBIN_HOOD_TRACE(x) \ - std::cout << __FUNCTION__ << "@" << __LINE__ << ": " << x << std::endl +# define ROBIN_HOOD_TRACE(...) \ + std::cout << __FUNCTION__ << "@" << __LINE__ << ": " << __VA_ARGS__ << std::endl; #else # define ROBIN_HOOD_TRACE(x) #endif +// #define ROBIN_HOOD_COUNT_ENABLED +#ifdef ROBIN_HOOD_COUNT_ENABLED +# include +# define ROBIN_HOOD_COUNT(x) ++counts().x; +namespace robin_hood { +struct Counts { + uint64_t shiftUp{}; + uint64_t shiftDown{}; +}; +inline std::ostream& operator<<(std::ostream& os, Counts const& c) { + return os << c.shiftUp << " shiftUp" << std::endl << c.shiftDown << " shiftDown" << std::endl; +} + +static Counts& counts() { + static Counts counts{}; + return counts; +} +} // namespace robin_hood +#else +# define ROBIN_HOOD_COUNT(x) +#endif + // all non-argument macros should use this facility. See // https://www.fluentcpp.com/2019/05/28/better-macros-better-flags/ #define ROBIN_HOOD(x) ROBIN_HOOD_PRIVATE_DEFINITION_##x() @@ -82,7 +109,7 @@ #endif // endianess -#ifdef _WIN32 +#ifdef _MSC_VER # define ROBIN_HOOD_PRIVATE_DEFINITION_LITTLE_ENDIAN() 1 # define ROBIN_HOOD_PRIVATE_DEFINITION_BIG_ENDIAN() 0 #else @@ -92,38 +119,46 @@ #endif // inline -#ifdef _WIN32 +#ifdef _MSC_VER # define ROBIN_HOOD_PRIVATE_DEFINITION_NOINLINE() __declspec(noinline) #else # define ROBIN_HOOD_PRIVATE_DEFINITION_NOINLINE() __attribute__((noinline)) #endif -// count leading/trailing bits -#ifdef _WIN32 -# if ROBIN_HOOD(BITNESS) == 32 -# define ROBIN_HOOD_PRIVATE_DEFINITION_BITSCANFORWARD() _BitScanForward -# else -# define ROBIN_HOOD_PRIVATE_DEFINITION_BITSCANFORWARD() _BitScanForward64 -# endif -# include -# pragma intrinsic(ROBIN_HOOD(BITSCANFORWARD)) -# define ROBIN_HOOD_COUNT_TRAILING_ZEROES(x) \ - [](size_t mask) noexcept->int { \ - unsigned long index; \ - return ROBIN_HOOD(BITSCANFORWARD)(&index, mask) ? static_cast(index) \ - : ROBIN_HOOD(BITNESS); \ - } \ - (x) +// exceptions +#if !defined(__cpp_exceptions) && !defined(__EXCEPTIONS) && !defined(_CPPUNWIND) +# define ROBIN_HOOD_PRIVATE_DEFINITION_HAS_EXCEPTIONS() 0 #else -# if ROBIN_HOOD(BITNESS) == 32 -# define ROBIN_HOOD_PRIVATE_DEFINITION_CTZ() __builtin_ctzl -# define ROBIN_HOOD_PRIVATE_DEFINITION_CLZ() __builtin_clzl +# define ROBIN_HOOD_PRIVATE_DEFINITION_HAS_EXCEPTIONS() 1 +#endif + +// count leading/trailing bits +#if !defined(ROBIN_HOOD_DISABLE_INTRINSICS) +# ifdef _MSC_VER +# if ROBIN_HOOD(BITNESS) == 32 +# define ROBIN_HOOD_PRIVATE_DEFINITION_BITSCANFORWARD() _BitScanForward +# else +# define ROBIN_HOOD_PRIVATE_DEFINITION_BITSCANFORWARD() _BitScanForward64 +# endif +# include +# pragma intrinsic(ROBIN_HOOD(BITSCANFORWARD)) +# define ROBIN_HOOD_COUNT_TRAILING_ZEROES(x) \ + [](size_t mask) noexcept -> int { \ + unsigned long index; \ + return ROBIN_HOOD(BITSCANFORWARD)(&index, mask) ? static_cast(index) \ + : ROBIN_HOOD(BITNESS); \ + }(x) # else -# define ROBIN_HOOD_PRIVATE_DEFINITION_CTZ() __builtin_ctzll -# define ROBIN_HOOD_PRIVATE_DEFINITION_CLZ() __builtin_clzll +# if ROBIN_HOOD(BITNESS) == 32 +# define ROBIN_HOOD_PRIVATE_DEFINITION_CTZ() __builtin_ctzl +# define ROBIN_HOOD_PRIVATE_DEFINITION_CLZ() __builtin_clzl +# else +# define ROBIN_HOOD_PRIVATE_DEFINITION_CTZ() __builtin_ctzll +# define ROBIN_HOOD_PRIVATE_DEFINITION_CLZ() __builtin_clzll +# endif +# define ROBIN_HOOD_COUNT_LEADING_ZEROES(x) ((x) ? ROBIN_HOOD(CLZ)(x) : ROBIN_HOOD(BITNESS)) +# define ROBIN_HOOD_COUNT_TRAILING_ZEROES(x) ((x) ? ROBIN_HOOD(CTZ)(x) : ROBIN_HOOD(BITNESS)) # endif -# define ROBIN_HOOD_COUNT_LEADING_ZEROES(x) ((x) ? ROBIN_HOOD(CLZ)(x) : ROBIN_HOOD(BITNESS)) -# define ROBIN_HOOD_COUNT_TRAILING_ZEROES(x) ((x) ? ROBIN_HOOD(CTZ)(x) : ROBIN_HOOD(BITNESS)) #endif // fallthrough @@ -139,7 +174,7 @@ #endif // likely/unlikely -#if defined(_WIN32) +#ifdef _MSC_VER # define ROBIN_HOOD_LIKELY(condition) condition # define ROBIN_HOOD_UNLIKELY(condition) condition #else @@ -147,6 +182,28 @@ # define ROBIN_HOOD_UNLIKELY(condition) __builtin_expect(condition, 0) #endif +// detect if native wchar_t type is availiable in MSVC +#ifdef _MSC_VER +# ifdef _NATIVE_WCHAR_T_DEFINED +# define ROBIN_HOOD_PRIVATE_DEFINITION_HAS_NATIVE_WCHART() 1 +# else +# define ROBIN_HOOD_PRIVATE_DEFINITION_HAS_NATIVE_WCHART() 0 +# endif +#else +# define ROBIN_HOOD_PRIVATE_DEFINITION_HAS_NATIVE_WCHART() 1 +#endif + +// detect if MSVC supports the pair(std::piecewise_construct_t,...) consructor being constexpr +#ifdef _MSC_VER +# if _MSC_VER <= 1900 +# define ROBIN_HOOD_PRIVATE_DEFINITION_BROKEN_CONSTEXPR() 1 +# else +# define ROBIN_HOOD_PRIVATE_DEFINITION_BROKEN_CONSTEXPR() 0 +# endif +#else +# define ROBIN_HOOD_PRIVATE_DEFINITION_BROKEN_CONSTEXPR() 0 +#endif + // workaround missing "is_trivially_copyable" in g++ < 5.0 // See https://stackoverflow.com/a/31798726/48181 #if defined(__GNUC__) && __GNUC__ < 5 @@ -246,39 +303,18 @@ using index_sequence_for = make_index_sequence; namespace detail { -// umul -#if defined(__SIZEOF_INT128__) -# define ROBIN_HOOD_PRIVATE_DEFINITION_HAS_UMUL128() 1 -# if defined(__GNUC__) || defined(__clang__) -# pragma GCC diagnostic push -# pragma GCC diagnostic ignored "-Wpedantic" -using uint128_t = unsigned __int128; -# pragma GCC diagnostic pop -# endif -inline uint64_t umul128(uint64_t a, uint64_t b, uint64_t* high) noexcept { - auto result = static_cast(a) * static_cast(b); - *high = static_cast(result >> 64U); - return static_cast(result); -} -#elif (defined(_WIN32) && ROBIN_HOOD(BITNESS) == 64) -# define ROBIN_HOOD_PRIVATE_DEFINITION_HAS_UMUL128() 1 -# include // for __umulh -# pragma intrinsic(__umulh) -# ifndef _M_ARM64 -# pragma intrinsic(_umul128) -# endif -inline uint64_t umul128(uint64_t a, uint64_t b, uint64_t* high) noexcept { -# ifdef _M_ARM64 - *high = __umulh(a, b); - return ((uint64_t)(a)) * (b); -# else - return _umul128(a, b, high); -# endif -} +// make sure we static_cast to the correct type for hash_int +#if ROBIN_HOOD(BITNESS) == 64 +using SizeT = uint64_t; #else -# define ROBIN_HOOD_PRIVATE_DEFINITION_HAS_UMUL128() 0 +using SizeT = uint32_t; #endif +template +T rotr(T x, unsigned k) { + return (x >> k) | (x << (8U * sizeof(T) - k)); +} + // This cast gets rid of warnings like "cast from 'uint8_t*' {aka 'unsigned char*'} to // 'uint64_t*' {aka 'long unsigned int*'} increases required alignment of target type". Use with // care! @@ -295,11 +331,17 @@ inline T reinterpret_cast_no_cast_align_warning(void const* ptr) noexcept { // make sure this is not inlined as it is slow and dramatically enlarges code, thus making other // inlinings more difficult. Throws are also generally the slow path. template -ROBIN_HOOD(NOINLINE) -void doThrow(Args&&... args) { +[[noreturn]] ROBIN_HOOD(NOINLINE) +#if ROBIN_HOOD(HAS_EXCEPTIONS) + void doThrow(Args&&... args) { // NOLINTNEXTLINE(cppcoreguidelines-pro-bounds-array-to-pointer-decay) throw E(std::forward(args)...); } +#else + void doThrow(Args&&... ROBIN_HOOD_UNUSED(args) /*unused*/) { + abort(); +} +#endif template T* assertNotNull(T* t, Args&&... args) { @@ -348,6 +390,7 @@ class BulkPoolAllocator { } BulkPoolAllocator& + // NOLINTNEXTLINE(bugprone-unhandled-self-assignment,cert-oop54-cpp) operator=(const BulkPoolAllocator& ROBIN_HOOD_UNUSED(o) /*unused*/) noexcept { // does not do anything return *this; @@ -361,7 +404,8 @@ class BulkPoolAllocator { void reset() noexcept { while (mListForFree) { T* tmp = *mListForFree; - free(mListForFree); + ROBIN_HOOD_LOG("std::free") + std::free(mListForFree); mListForFree = reinterpret_cast_no_cast_align_warning(tmp); } mHead = nullptr; @@ -396,8 +440,10 @@ class BulkPoolAllocator { // calculate number of available elements in ptr if (numBytes < ALIGNMENT + ALIGNED_SIZE) { // not enough data for at least one element. Free and return. - free(ptr); + ROBIN_HOOD_LOG("std::free") + std::free(ptr); } else { + ROBIN_HOOD_LOG("add to buffer") add(ptr, numBytes); } } @@ -438,10 +484,10 @@ class BulkPoolAllocator { mListForFree = data; // create linked list for newly allocated data - auto const headT = + auto* const headT = reinterpret_cast_no_cast_align_warning(reinterpret_cast(ptr) + ALIGNMENT); - auto const head = reinterpret_cast(headT); + auto* const head = reinterpret_cast(headT); // Visual Studio compiler automatically unrolls this loop, which is pretty cool for (size_t i = 0; i < numElements; ++i) { @@ -461,9 +507,10 @@ class BulkPoolAllocator { size_t const numElementsToAlloc = calcNumElementsToAlloc(); // alloc new memory: [prev |T, T, ... T] - // std::cout << (sizeof(T*) + ALIGNED_SIZE * numElementsToAlloc) << " bytes" << std::endl; size_t const bytes = ALIGNMENT + ALIGNED_SIZE * numElementsToAlloc; - add(assertNotNull(malloc(bytes)), bytes); + ROBIN_HOOD_LOG("std::malloc " << bytes << " = " << ALIGNMENT << " + " << ALIGNED_SIZE + << " * " << numElementsToAlloc) + add(assertNotNull(std::malloc(bytes)), bytes); return mHead; } @@ -490,7 +537,7 @@ class BulkPoolAllocator { T** mListForFree{nullptr}; }; -template +template struct NodeAllocator; // dummy allocator that does nothing @@ -499,30 +546,29 @@ struct NodeAllocator { // we are not using the data, so just free it. void addOrFree(void* ptr, size_t ROBIN_HOOD_UNUSED(numBytes) /*unused*/) noexcept { - free(ptr); + ROBIN_HOOD_LOG("std::free") + std::free(ptr); } }; template struct NodeAllocator : public BulkPoolAllocator {}; -// dummy hash, unsed as mixer when robin_hood::hash is already used -template -struct identity_hash { - constexpr size_t operator()(T const& obj) const noexcept { - return static_cast(obj); - } -}; - // c++14 doesn't have is_nothrow_swappable, and clang++ 6.0.1 doesn't like it either, so I'm making // my own here. namespace swappable { +#if ROBIN_HOOD(CXX) < ROBIN_HOOD(CXX17) using std::swap; template struct nothrow { static const bool value = noexcept(swap(std::declval(), std::declval())); }; - +#else +template +struct nothrow { + static const bool value = std::is_nothrow_swappable::value; +}; +#endif } // namespace swappable } // namespace detail @@ -537,9 +583,10 @@ struct pair { using first_type = T1; using second_type = T2; - template ::value && - std::is_default_constructible::value>::type> - constexpr pair() noexcept(noexcept(T1()) && noexcept(T2())) + template ::value && + std::is_default_constructible::value>::type> + constexpr pair() noexcept(noexcept(U1()) && noexcept(U2())) : first() , second() {} @@ -550,44 +597,46 @@ struct pair { , second(o.second) {} // pair constructors are explicit so we don't accidentally call this ctor when we don't have to. - explicit constexpr pair(std::pair&& o) noexcept( - noexcept(T1(std::move(std::declval()))) && - noexcept(T2(std::move(std::declval())))) + explicit constexpr pair(std::pair&& o) noexcept(noexcept( + T1(std::move(std::declval()))) && noexcept(T2(std::move(std::declval())))) : first(std::move(o.first)) , second(std::move(o.second)) {} - constexpr pair(T1&& a, T2&& b) noexcept(noexcept(T1(std::move(std::declval()))) && - noexcept(T2(std::move(std::declval())))) + constexpr pair(T1&& a, T2&& b) noexcept(noexcept( + T1(std::move(std::declval()))) && noexcept(T2(std::move(std::declval())))) : first(std::move(a)) , second(std::move(b)) {} template - constexpr pair(U1&& a, U2&& b) noexcept(noexcept(T1(std::forward(std::declval()))) && - noexcept(T2(std::forward(std::declval())))) + constexpr pair(U1&& a, U2&& b) noexcept(noexcept(T1(std::forward( + std::declval()))) && noexcept(T2(std::forward(std::declval())))) : first(std::forward(a)) , second(std::forward(b)) {} template - constexpr pair( - std::piecewise_construct_t /*unused*/, std::tuple a, - std::tuple b) noexcept(noexcept(pair(std::declval&>(), - std::declval&>(), - ROBIN_HOOD_STD::index_sequence_for(), - ROBIN_HOOD_STD::index_sequence_for()))) + // MSVC 2015 produces error "C2476: ‘constexpr’ constructor does not initialize all members" + // if this constructor is constexpr +#if !ROBIN_HOOD(BROKEN_CONSTEXPR) + constexpr +#endif + pair(std::piecewise_construct_t /*unused*/, std::tuple a, + std::tuple + b) noexcept(noexcept(pair(std::declval&>(), + std::declval&>(), + ROBIN_HOOD_STD::index_sequence_for(), + ROBIN_HOOD_STD::index_sequence_for()))) : pair(a, b, ROBIN_HOOD_STD::index_sequence_for(), - ROBIN_HOOD_STD::index_sequence_for()) {} + ROBIN_HOOD_STD::index_sequence_for()) { + } // constructor called from the std::piecewise_construct_t ctor template - pair(std::tuple& a, std::tuple& b, - ROBIN_HOOD_STD::index_sequence /*unused*/, - ROBIN_HOOD_STD::index_sequence< - I2...> /*unused*/) noexcept(noexcept(T1(std:: - forward(std::get( - std::declval< - std::tuple&>()))...)) && - noexcept(T2(std::forward( - std::get(std::declval&>()))...))) + pair(std::tuple& a, std::tuple& b, ROBIN_HOOD_STD::index_sequence /*unused*/, ROBIN_HOOD_STD::index_sequence /*unused*/) noexcept( + noexcept(T1(std::forward(std::get( + std::declval&>()))...)) && noexcept(T2(std:: + forward(std::get( + std::declval&>()))...))) : first(std::forward(std::get(a))...) , second(std::forward(std::get(b))...) { // make visual studio compiler happy about warning about unused a & b. @@ -596,19 +645,6 @@ struct pair { (void)b; } - ROBIN_HOOD(NODISCARD) first_type& getFirst() noexcept { - return first; - } - ROBIN_HOOD(NODISCARD) first_type const& getFirst() const noexcept { - return first; - } - ROBIN_HOOD(NODISCARD) second_type& getSecond() noexcept { - return second; - } - ROBIN_HOOD(NODISCARD) second_type const& getSecond() const noexcept { - return second; - } - void swap(pair& o) noexcept((detail::swappable::nothrow::value) && (detail::swappable::nothrow::value)) { using std::swap; @@ -621,19 +657,44 @@ struct pair { }; template -void swap(pair& a, pair& b) noexcept( +inline void swap(pair& a, pair& b) noexcept( noexcept(std::declval&>().swap(std::declval&>()))) { a.swap(b); } -// Hash an arbitrary amount of bytes. This is basically Murmur2 hash without caring about big -// endianness. TODO(martinus) add a fallback for very large strings? -static size_t hash_bytes(void const* ptr, size_t const len) noexcept { +template +inline constexpr bool operator==(pair const& x, pair const& y) { + return (x.first == y.first) && (x.second == y.second); +} +template +inline constexpr bool operator!=(pair const& x, pair const& y) { + return !(x == y); +} +template +inline constexpr bool operator<(pair const& x, pair const& y) noexcept(noexcept( + std::declval() < std::declval()) && noexcept(std::declval() < + std::declval())) { + return x.first < y.first || (!(y.first < x.first) && x.second < y.second); +} +template +inline constexpr bool operator>(pair const& x, pair const& y) { + return y < x; +} +template +inline constexpr bool operator<=(pair const& x, pair const& y) { + return !(x > y); +} +template +inline constexpr bool operator>=(pair const& x, pair const& y) { + return !(x < y); +} + +inline size_t hash_bytes(void const* ptr, size_t len) noexcept { static constexpr uint64_t m = UINT64_C(0xc6a4a7935bd1e995); static constexpr uint64_t seed = UINT64_C(0xe17a1465); static constexpr unsigned int r = 47; - auto const data64 = static_cast(ptr); + auto const* const data64 = static_cast(ptr); uint64_t h = seed ^ (len * m); size_t const n_blocks = len / 8; @@ -648,7 +709,7 @@ static size_t hash_bytes(void const* ptr, size_t const len) noexcept { h *= m; } - auto const data8 = reinterpret_cast(data64 + n_blocks); + auto const* const data8 = reinterpret_cast(data64 + n_blocks); switch (len & 7U) { case 7: h ^= static_cast(data8[6]) << 48U; @@ -677,65 +738,87 @@ static size_t hash_bytes(void const* ptr, size_t const len) noexcept { } h ^= h >> r; - h *= m; - h ^= h >> r; + + // not doing the final step here, because this will be done by keyToIdx anyways + // h *= m; + // h ^= h >> r; return static_cast(h); } -inline size_t hash_int(uint64_t obj) noexcept { -#if ROBIN_HOOD(HAS_UMUL128) - // 167079903232 masksum, 120428523 ops best: 0xde5fb9d2630458e9 - static constexpr uint64_t k = UINT64_C(0xde5fb9d2630458e9); - uint64_t h; - uint64_t l = detail::umul128(obj, k, &h); - return h + l; -#elif ROBIN_HOOD(BITNESS) == 32 - uint64_t const r = obj * UINT64_C(0xca4bcaa75ec3f625); - auto h = static_cast(r >> 32U); - auto l = static_cast(r); - return h + l; -#else - // murmurhash 3 finalizer - uint64_t h = obj; - h ^= h >> 33; - h *= 0xff51afd7ed558ccd; - h ^= h >> 33; - h *= 0xc4ceb9fe1a85ec53; - h ^= h >> 33; - return static_cast(h); -#endif +inline size_t hash_int(uint64_t x) noexcept { + // tried lots of different hashes, let's stick with murmurhash3. It's simple, fast, well tested, + // and doesn't need any special 128bit operations. + x ^= x >> 33U; + x *= UINT64_C(0xff51afd7ed558ccd); + x ^= x >> 33U; + + // not doing the final step here, because this will be done by keyToIdx anyways + // x *= UINT64_C(0xc4ceb9fe1a85ec53); + // x ^= x >> 33U; + return static_cast(x); } // A thin wrapper around std::hash, performing an additional simple mixing step of the result. -template +template struct hash : public std::hash { size_t operator()(T const& obj) const noexcept(noexcept(std::declval>().operator()(std::declval()))) { // call base hash auto result = std::hash::operator()(obj); // return mixed of that, to be save against identity has - return hash_int(static_cast(result)); + return hash_int(static_cast(result)); } }; -template <> -struct hash { - size_t operator()(std::string const& str) const noexcept { - return hash_bytes(str.data(), str.size()); +template +struct hash> { + size_t operator()(std::basic_string const& str) const noexcept { + return hash_bytes(str.data(), sizeof(CharT) * str.size()); + } +}; + +#if ROBIN_HOOD(CXX) >= ROBIN_HOOD(CXX17) +template +struct hash> { + size_t operator()(std::basic_string_view const& sv) const noexcept { + return hash_bytes(sv.data(), sizeof(CharT) * sv.size()); } }; +#endif template struct hash { size_t operator()(T* ptr) const noexcept { - return hash_int(reinterpret_cast(ptr)); + return hash_int(reinterpret_cast(ptr)); + } +}; + +template +struct hash> { + size_t operator()(std::unique_ptr const& ptr) const noexcept { + return hash_int(reinterpret_cast(ptr.get())); + } +}; + +template +struct hash> { + size_t operator()(std::shared_ptr const& ptr) const noexcept { + return hash_int(reinterpret_cast(ptr.get())); + } +}; + +template +struct hash::value>::type> { + size_t operator()(Enum e) const noexcept { + using Underlying = typename std::underlying_type::type; + return hash{}(static_cast(e)); } }; #define ROBIN_HOOD_HASH_INT(T) \ template <> \ struct hash { \ - size_t operator()(T obj) const noexcept { \ + size_t operator()(T const& obj) const noexcept { \ return hash_int(static_cast(obj)); \ } \ } @@ -751,7 +834,9 @@ ROBIN_HOOD_HASH_INT(signed char); ROBIN_HOOD_HASH_INT(unsigned char); ROBIN_HOOD_HASH_INT(char16_t); ROBIN_HOOD_HASH_INT(char32_t); +#if ROBIN_HOOD(HAS_NATIVE_WCHART) ROBIN_HOOD_HASH_INT(wchar_t); +#endif ROBIN_HOOD_HASH_INT(short); ROBIN_HOOD_HASH_INT(unsigned short); ROBIN_HOOD_HASH_INT(int); @@ -765,10 +850,38 @@ ROBIN_HOOD_HASH_INT(unsigned long long); #endif namespace detail { +template +struct void_type { + using type = void; +}; + +template +struct has_is_transparent : public std::false_type {}; + +template +struct has_is_transparent::type> + : public std::true_type {}; + +// using wrapper classes for hash and key_equal prevents the diamond problem when the same type +// is used. see https://stackoverflow.com/a/28771920/48181 +template +struct WrapHash : public T { + WrapHash() = default; + explicit WrapHash(T const& o) noexcept(noexcept(T(std::declval()))) + : T(o) {} +}; + +template +struct WrapKeyEqual : public T { + WrapKeyEqual() = default; + explicit WrapKeyEqual(T const& o) noexcept(noexcept(T(std::declval()))) + : T(o) {} +}; + // A highly optimized hashmap implementation, using the Robin Hood algorithm. // -// In most cases, this map should be usable as a drop-in replacement for std::unordered_map, but be -// about 2x faster in most cases and require much less allocations. +// In most cases, this map should be usable as a drop-in replacement for std::unordered_map, but +// be about 2x faster in most cases and require much less allocations. // // This implementation uses the following memory layout: // @@ -776,8 +889,8 @@ namespace detail { // // * Node: either a DataNode that directly has the std::pair as member, // or a DataNode with a pointer to std::pair. Which DataNode representation to use -// depends on how fast the swap() operation is. Heuristically, this is automatically choosen based -// on sizeof(). there are always 2^n Nodes. +// depends on how fast the swap() operation is. Heuristically, this is automatically choosen +// based on sizeof(). there are always 2^n Nodes. // // * info: Each Node in the map has a corresponding info byte, so there are 2^n info bytes. // Each byte is initialized to 0, meaning the corresponding Node is empty. Set to 1 means the @@ -785,45 +898,55 @@ namespace detail { // actually belongs to the previous position and was pushed out because that place is already // taken. // -// * infoSentinel: Sentinel byte set to 1, so that iterator's ++ can stop at end() without the need -// for a idx -// variable. +// * infoSentinel: Sentinel byte set to 1, so that iterator's ++ can stop at end() without the +// need for a idx variable. // -// According to STL, order of templates has effect on throughput. That's why I've moved the boolean -// to the front. +// According to STL, order of templates has effect on throughput. That's why I've moved the +// boolean to the front. // https://www.reddit.com/r/cpp/comments/ahp6iu/compile_time_binary_size_reductions_and_cs_future/eeguck4/ -template -class unordered_map - : public Hash, - public KeyEqual, +class Table + : public WrapHash, + public WrapKeyEqual, detail::NodeAllocator< - robin_hood::pair::type, T>, 4, 16384, - IsFlatMap> { + typename std::conditional< + std::is_void::value, Key, + robin_hood::pair::type, T>>::type, + 4, 16384, IsFlat> { public: + static constexpr bool is_flat = IsFlat; + static constexpr bool is_map = !std::is_void::value; + static constexpr bool is_set = !is_map; + static constexpr bool is_transparent = + has_is_transparent::value && has_is_transparent::value; + using key_type = Key; using mapped_type = T; - using value_type = - robin_hood::pair::type, T>; + using value_type = typename std::conditional< + is_set, Key, + robin_hood::pair::type, T>>::type; using size_type = size_t; using hasher = Hash; using key_equal = KeyEqual; - using Self = - unordered_map; - static constexpr bool is_flat_map = IsFlatMap; + using Self = Table; private: static_assert(MaxLoadFactor100 > 10 && MaxLoadFactor100 < 100, "MaxLoadFactor100 needs to be >10 && < 100"); + using WHash = WrapHash; + using WKeyEqual = WrapKeyEqual; + // configuration defaults // make sure we have 8 elements, needed to quickly rehash mInfo static constexpr size_t InitialNumElements = sizeof(uint64_t); static constexpr uint32_t InitialInfoNumBits = 5; static constexpr uint8_t InitialInfoInc = 1U << InitialInfoNumBits; - static constexpr uint8_t InitialInfoHashShift = sizeof(size_t) * 8 - InitialInfoNumBits; - using DataPool = detail::NodeAllocator; + static constexpr size_t InfoMask = InitialInfoInc - 1U; + static constexpr uint8_t InitialInfoHashShift = 0; + using DataPool = detail::NodeAllocator; // type needs to be wider than uint8_t. using InfoType = uint32_t; @@ -831,8 +954,8 @@ class unordered_map // DataNode //////////////////////////////////////////////////////// // Primary template for the data node. We have special implementations for small and big - // objects. For large objects it is assumed that swap() is fairly slow, so we allocate these on - // the heap so swap merely swaps a pointer. + // objects. For large objects it is assumed that swap() is fairly slow, so we allocate these + // on the heap so swap merely swaps a pointer. template class DataNode {}; @@ -868,19 +991,38 @@ class unordered_map return mData; } - ROBIN_HOOD(NODISCARD) typename value_type::first_type& getFirst() noexcept { + template + ROBIN_HOOD(NODISCARD) + typename std::enable_if::type getFirst() noexcept { return mData.first; } + template + ROBIN_HOOD(NODISCARD) + typename std::enable_if::type getFirst() noexcept { + return mData; + } - ROBIN_HOOD(NODISCARD) typename value_type::first_type const& getFirst() const noexcept { + template + ROBIN_HOOD(NODISCARD) + typename std::enable_if::type + getFirst() const noexcept { return mData.first; } + template + ROBIN_HOOD(NODISCARD) + typename std::enable_if::type getFirst() const noexcept { + return mData; + } - ROBIN_HOOD(NODISCARD) typename value_type::second_type& getSecond() noexcept { + template + ROBIN_HOOD(NODISCARD) + typename std::enable_if::type getSecond() noexcept { return mData.second; } - ROBIN_HOOD(NODISCARD) typename value_type::second_type const& getSecond() const noexcept { + template + ROBIN_HOOD(NODISCARD) + typename std::enable_if::type getSecond() const noexcept { return mData.second; } @@ -932,19 +1074,38 @@ class unordered_map return *mData; } - ROBIN_HOOD(NODISCARD) typename value_type::first_type& getFirst() { + template + ROBIN_HOOD(NODISCARD) + typename std::enable_if::type getFirst() noexcept { return mData->first; } + template + ROBIN_HOOD(NODISCARD) + typename std::enable_if::type getFirst() noexcept { + return *mData; + } - ROBIN_HOOD(NODISCARD) typename value_type::first_type const& getFirst() const { + template + ROBIN_HOOD(NODISCARD) + typename std::enable_if::type + getFirst() const noexcept { return mData->first; } + template + ROBIN_HOOD(NODISCARD) + typename std::enable_if::type getFirst() const noexcept { + return *mData; + } - ROBIN_HOOD(NODISCARD) typename value_type::second_type& getSecond() { + template + ROBIN_HOOD(NODISCARD) + typename std::enable_if::type getSecond() noexcept { return mData->second; } - ROBIN_HOOD(NODISCARD) typename value_type::second_type const& getSecond() const { + template + ROBIN_HOOD(NODISCARD) + typename std::enable_if::type getSecond() const noexcept { return mData->second; } @@ -957,7 +1118,26 @@ class unordered_map value_type* mData; }; - using Node = DataNode; + using Node = DataNode; + + // helpers for insertKeyPrepareEmptySpot: extract first entry (only const required) + ROBIN_HOOD(NODISCARD) key_type const& getFirstConst(Node const& n) const noexcept { + return n.getFirst(); + } + + // in case we have void mapped_type, we are not using a pair, thus we just route k through. + // No need to disable this because it's just not used if not applicable. + ROBIN_HOOD(NODISCARD) key_type const& getFirstConst(key_type const& k) const noexcept { + return k; + } + + // in case we have non-void mapped_type, we have a standard robin_hood::pair + template + ROBIN_HOOD(NODISCARD) + typename std::enable_if::value, key_type const&>::type + getFirstConst(value_type const& vt) const noexcept { + return vt.first; + } // Cloner ////////////////////////////////////////////////////////// @@ -968,20 +1148,20 @@ class unordered_map template struct Cloner { void operator()(M const& source, M& target) const { - // std::memcpy(target.mKeyVals, source.mKeyVals, - // target.calcNumBytesTotal(target.mMask + 1)); - auto src = reinterpret_cast(source.mKeyVals); - auto tgt = reinterpret_cast(target.mKeyVals); - std::copy(src, src + target.calcNumBytesTotal(target.mMask + 1), tgt); + auto const* const src = reinterpret_cast(source.mKeyVals); + auto* tgt = reinterpret_cast(target.mKeyVals); + auto const numElementsWithBuffer = target.calcNumElementsWithBuffer(target.mMask + 1); + std::copy(src, src + target.calcNumBytesTotal(numElementsWithBuffer), tgt); } }; template struct Cloner { void operator()(M const& s, M& t) const { - std::copy(s.mInfo, s.mInfo + t.calcNumBytesInfo(t.mMask + 1), t.mInfo); + auto const numElementsWithBuffer = t.calcNumElementsWithBuffer(t.mMask + 1); + std::copy(s.mInfo, s.mInfo + t.calcNumBytesInfo(numElementsWithBuffer), t.mInfo); - for (size_t i = 0; i < t.mMask + 1; ++i) { + for (size_t i = 0; i < numElementsWithBuffer; ++i) { if (t.mInfo[i]) { ::new (static_cast(t.mKeyVals + i)) Node(t, *s.mKeyVals[i]); } @@ -991,7 +1171,7 @@ class unordered_map // Destroyer /////////////////////////////////////////////////////// - template + template struct Destroyer {}; template @@ -1010,7 +1190,9 @@ class unordered_map void nodes(M& m) const noexcept { m.mNumElements = 0; // clear also resets mInfo to 0, that's sometimes not necessary. - for (size_t idx = 0; idx <= m.mMask; ++idx) { + auto const numElementsWithBuffer = m.calcNumElementsWithBuffer(m.mMask + 1); + + for (size_t idx = 0; idx < numElementsWithBuffer; ++idx) { if (0 != m.mInfo[idx]) { Node& n = m.mKeyVals[idx]; n.destroy(m); @@ -1022,7 +1204,8 @@ class unordered_map void nodesDoNotDeallocate(M& m) const noexcept { m.mNumElements = 0; // clear also resets mInfo to 0, that's sometimes not necessary. - for (size_t idx = 0; idx <= m.mMask; ++idx) { + auto const numElementsWithBuffer = m.calcNumElementsWithBuffer(m.mMask + 1); + for (size_t idx = 0; idx < numElementsWithBuffer; ++idx) { if (0 != m.mInfo[idx]) { Node& n = m.mKeyVals[idx]; n.destroyDoNotDeallocate(); @@ -1054,8 +1237,8 @@ class unordered_map // compared to end(). Iter() = default; - // Rule of zero: nothing specified. The conversion constructor is only enabled for iterator - // to const_iterator, so it doesn't accidentally work as a copy ctor. + // Rule of zero: nothing specified. The conversion constructor is only enabled for + // iterator to const_iterator, so it doesn't accidentally work as a copy ctor. // Conversion constructor from iterator to const_iterator. template (mInfo); -#if ROBIN_HOOD(LITTLE_ENDIAN) - inc = ROBIN_HOOD_COUNT_TRAILING_ZEROES(n) / 8; + size_t n = 0; + while (0U == (n = detail::unaligned_load(mInfo))) { + mInfo += sizeof(size_t); + mKeyVals += sizeof(size_t); + } +#if defined(ROBIN_HOOD_DISABLE_INTRINSICS) + // we know for certain that within the next 8 bytes we'll find a non-zero one. + if (ROBIN_HOOD_UNLIKELY(0U == detail::unaligned_load(mInfo))) { + mInfo += 4; + mKeyVals += 4; + } + if (ROBIN_HOOD_UNLIKELY(0U == detail::unaligned_load(mInfo))) { + mInfo += 2; + mKeyVals += 2; + } + if (ROBIN_HOOD_UNLIKELY(0U == *mInfo)) { + mInfo += 1; + mKeyVals += 1; + } #else - inc = ROBIN_HOOD_COUNT_LEADING_ZEROES(n) / 8; +# if ROBIN_HOOD(LITTLE_ENDIAN) + auto inc = ROBIN_HOOD_COUNT_TRAILING_ZEROES(n) / 8; +# else + auto inc = ROBIN_HOOD_COUNT_LEADING_ZEROES(n) / 8; +# endif + mInfo += inc; + mKeyVals += inc; #endif - mInfo += inc; - mKeyVals += inc; - } while (inc == static_cast(sizeof(size_t))); } - friend class unordered_map; + friend class Table; NodePtr mKeyVals{nullptr}; uint8_t const* mInfo{nullptr}; }; @@ -1139,22 +1346,22 @@ class unordered_map // The upper 1-5 bits need to be a reasonable good hash, to save comparisons. template void keyToIdx(HashKey&& key, size_t* idx, InfoType* info) const { - // for a user-specified hash that is *not* robin_hood::hash, apply robin_hood::hash as an - // additional mixing step. This serves as a bad hash prevention, if the given data is badly - // mixed. - using Mix = - typename std::conditional, hasher>::value, - ::robin_hood::detail::identity_hash, - ::robin_hood::hash>::type; - *idx = Mix{}(Hash::operator()(key)); + // In addition to whatever hash is used, add another mul & shift so we get better hashing. + // This serves as a bad hash prevention, if the given data is + // badly mixed. + auto h = static_cast(WHash::operator()(key)); + + h *= mHashMultiplier; + h ^= h >> 33U; - *info = mInfoInc + static_cast(*idx >> mInfoHashShift); - *idx &= mMask; + // the lower InitialInfoNumBits are reserved for info. + *info = mInfoInc + static_cast((h & InfoMask) >> mInfoHashShift); + *idx = (static_cast(h) >> InitialInfoNumBits) & mMask; } // forwards the index by one, wrapping around at the end void next(InfoType* info, size_t* idx) const noexcept { - *idx = (*idx + 1) & mMask; + *idx = *idx + 1; *info += mInfoInc; } @@ -1166,38 +1373,38 @@ class unordered_map } // Shift everything up by one element. Tries to move stuff around. - // True if some shifting has occured (entry under idx is a constructed object) - // Fals if no shift has occured (entry under idx is unconstructed memory) void - shiftUp(size_t idx, + shiftUp(size_t startIdx, size_t const insertion_idx) noexcept(std::is_nothrow_move_assignable::value) { + auto idx = startIdx; + ::new (static_cast(mKeyVals + idx)) Node(std::move(mKeyVals[idx - 1])); + while (--idx != insertion_idx) { + mKeyVals[idx] = std::move(mKeyVals[idx - 1]); + } + + idx = startIdx; while (idx != insertion_idx) { - size_t prev_idx = (idx - 1) & mMask; - if (mInfo[idx]) { - mKeyVals[idx] = std::move(mKeyVals[prev_idx]); - } else { - ::new (static_cast(mKeyVals + idx)) Node(std::move(mKeyVals[prev_idx])); - } - mInfo[idx] = static_cast(mInfo[prev_idx] + mInfoInc); + ROBIN_HOOD_COUNT(shiftUp) + mInfo[idx] = static_cast(mInfo[idx - 1] + mInfoInc); if (ROBIN_HOOD_UNLIKELY(mInfo[idx] + mInfoInc > 0xFF)) { mMaxNumElementsAllowed = 0; } - idx = prev_idx; + --idx; } } void shiftDown(size_t idx) noexcept(std::is_nothrow_move_assignable::value) { // until we find one that is either empty or has zero offset. - // TODO(martinus) we don't need to move everything, just the last one for the same bucket. + // TODO(martinus) we don't need to move everything, just the last one for the same + // bucket. mKeyVals[idx].destroy(*this); // until we find one that is either empty or has zero offset. - size_t nextIdx = (idx + 1) & mMask; - while (mInfo[nextIdx] >= 2 * mInfoInc) { - mInfo[idx] = static_cast(mInfo[nextIdx] - mInfoInc); - mKeyVals[idx] = std::move(mKeyVals[nextIdx]); - idx = nextIdx; - nextIdx = (idx + 1) & mMask; + while (mInfo[idx + 1] >= 2 * mInfoInc) { + ROBIN_HOOD_COUNT(shiftDown) + mInfo[idx] = static_cast(mInfo[idx + 1] - mInfoInc); + mKeyVals[idx] = std::move(mKeyVals[idx + 1]); + ++idx; } mInfo[idx] = 0; @@ -1210,46 +1417,50 @@ class unordered_map template ROBIN_HOOD(NODISCARD) size_t findIdx(Other const& key) const { - size_t idx; - InfoType info; + size_t idx{}; + InfoType info{}; keyToIdx(key, &idx, &info); do { // unrolling this twice gives a bit of a speedup. More unrolling did not help. - if (info == mInfo[idx] && KeyEqual::operator()(key, mKeyVals[idx].getFirst())) { + if (info == mInfo[idx] && + ROBIN_HOOD_LIKELY(WKeyEqual::operator()(key, mKeyVals[idx].getFirst()))) { return idx; } next(&info, &idx); - if (info == mInfo[idx] && KeyEqual::operator()(key, mKeyVals[idx].getFirst())) { + if (info == mInfo[idx] && + ROBIN_HOOD_LIKELY(WKeyEqual::operator()(key, mKeyVals[idx].getFirst()))) { return idx; } next(&info, &idx); } while (info <= mInfo[idx]); // nothing found! - return mMask == 0 ? 0 : mMask + 1; + return mMask == 0 ? 0 + : static_cast(std::distance( + mKeyVals, reinterpret_cast_no_cast_align_warning(mInfo))); } - void cloneData(const unordered_map& o) { - Cloner()(o, *this); + void cloneData(const Table& o) { + Cloner()(o, *this); } // inserts a keyval that is guaranteed to be new, e.g. when the hashmap is resized. - // @return index where the element was created - size_t insert_move(Node&& keyval) { + // @return True on success, false if something went wrong + void insert_move(Node&& keyval) { // we don't retry, fail if overflowing // don't need to check max num elements if (0 == mMaxNumElementsAllowed && !try_increase_info()) { throwOverflowError(); } - size_t idx; - InfoType info; + size_t idx{}; + InfoType info{}; keyToIdx(keyval.getFirst(), &idx, &info); // skip forward. Use <= because we are certain that the element is not there. while (info <= mInfo[idx]) { - idx = (idx + 1) & mMask; + idx = idx + 1; info += mInfoInc; } @@ -1277,51 +1488,56 @@ class unordered_map mInfo[insertion_idx] = insertion_info; ++mNumElements; - return insertion_idx; } public: using iterator = Iter; using const_iterator = Iter; - // Creates an empty hash map. Nothing is allocated yet, this happens at the first insert. This - // tremendously speeds up ctor & dtor of a map that never receives an element. The penalty is - // payed at the first insert, and not before. Lookup of this empty map works because everybody - // points to DummyInfoByte::b. parameter bucket_count is dictated by the standard, but we can - // ignore it. - explicit unordered_map(size_t ROBIN_HOOD_UNUSED(bucket_count) /*unused*/ = 0, - const Hash& h = Hash{}, - const KeyEqual& equal = KeyEqual{}) noexcept(noexcept(Hash(h)) && - noexcept(KeyEqual(equal))) - : Hash(h) - , KeyEqual(equal) { - ROBIN_HOOD_TRACE(this); + Table() noexcept(noexcept(Hash()) && noexcept(KeyEqual())) + : WHash() + , WKeyEqual() { + ROBIN_HOOD_TRACE(this) + } + + // Creates an empty hash map. Nothing is allocated yet, this happens at the first insert. + // This tremendously speeds up ctor & dtor of a map that never receives an element. The + // penalty is payed at the first insert, and not before. Lookup of this empty map works + // because everybody points to DummyInfoByte::b. parameter bucket_count is dictated by the + // standard, but we can ignore it. + explicit Table( + size_t ROBIN_HOOD_UNUSED(bucket_count) /*unused*/, const Hash& h = Hash{}, + const KeyEqual& equal = KeyEqual{}) noexcept(noexcept(Hash(h)) && noexcept(KeyEqual(equal))) + : WHash(h) + , WKeyEqual(equal) { + ROBIN_HOOD_TRACE(this) } template - unordered_map(Iter first, Iter last, size_t ROBIN_HOOD_UNUSED(bucket_count) /*unused*/ = 0, - const Hash& h = Hash{}, const KeyEqual& equal = KeyEqual{}) - : Hash(h) - , KeyEqual(equal) { - ROBIN_HOOD_TRACE(this); + Table(Iter first, Iter last, size_t ROBIN_HOOD_UNUSED(bucket_count) /*unused*/ = 0, + const Hash& h = Hash{}, const KeyEqual& equal = KeyEqual{}) + : WHash(h) + , WKeyEqual(equal) { + ROBIN_HOOD_TRACE(this) insert(first, last); } - unordered_map(std::initializer_list initlist, - size_t ROBIN_HOOD_UNUSED(bucket_count) /*unused*/ = 0, const Hash& h = Hash{}, - const KeyEqual& equal = KeyEqual{}) - : Hash(h) - , KeyEqual(equal) { - ROBIN_HOOD_TRACE(this); + Table(std::initializer_list initlist, + size_t ROBIN_HOOD_UNUSED(bucket_count) /*unused*/ = 0, const Hash& h = Hash{}, + const KeyEqual& equal = KeyEqual{}) + : WHash(h) + , WKeyEqual(equal) { + ROBIN_HOOD_TRACE(this) insert(initlist.begin(), initlist.end()); } - unordered_map(unordered_map&& o) noexcept - : Hash(std::move(static_cast(o))) - , KeyEqual(std::move(static_cast(o))) + Table(Table&& o) noexcept + : WHash(std::move(static_cast(o))) + , WKeyEqual(std::move(static_cast(o))) , DataPool(std::move(static_cast(o))) { - ROBIN_HOOD_TRACE(this); + ROBIN_HOOD_TRACE(this) if (o.mMask) { + mHashMultiplier = std::move(o.mHashMultiplier); mKeyVals = std::move(o.mKeyVals); mInfo = std::move(o.mInfo); mNumElements = std::move(o.mNumElements); @@ -1334,12 +1550,13 @@ class unordered_map } } - unordered_map& operator=(unordered_map&& o) noexcept { - ROBIN_HOOD_TRACE(this); + Table& operator=(Table&& o) noexcept { + ROBIN_HOOD_TRACE(this) if (&o != this) { if (o.mMask) { // only move stuff if the other map actually has some data destroy(); + mHashMultiplier = std::move(o.mHashMultiplier); mKeyVals = std::move(o.mKeyVals); mInfo = std::move(o.mInfo); mNumElements = std::move(o.mNumElements); @@ -1347,8 +1564,8 @@ class unordered_map mMaxNumElementsAllowed = std::move(o.mMaxNumElementsAllowed); mInfoInc = std::move(o.mInfoInc); mInfoHashShift = std::move(o.mInfoHashShift); - Hash::operator=(std::move(static_cast(o))); - KeyEqual::operator=(std::move(static_cast(o))); + WHash::operator=(std::move(static_cast(o))); + WKeyEqual::operator=(std::move(static_cast(o))); DataPool::operator=(std::move(static_cast(o))); o.init(); @@ -1361,19 +1578,25 @@ class unordered_map return *this; } - unordered_map(const unordered_map& o) - : Hash(static_cast(o)) - , KeyEqual(static_cast(o)) + Table(const Table& o) + : WHash(static_cast(o)) + , WKeyEqual(static_cast(o)) , DataPool(static_cast(o)) { - ROBIN_HOOD_TRACE(this); + ROBIN_HOOD_TRACE(this) if (!o.empty()) { // not empty: create an exact copy. it is also possible to just iterate through all // elements and insert them, but copying is probably faster. + auto const numElementsWithBuffer = calcNumElementsWithBuffer(o.mMask + 1); + auto const numBytesTotal = calcNumBytesTotal(numElementsWithBuffer); + + ROBIN_HOOD_LOG("std::malloc " << numBytesTotal << " = calcNumBytesTotal(" + << numElementsWithBuffer << ")") + mHashMultiplier = o.mHashMultiplier; mKeyVals = static_cast( - detail::assertNotNull(malloc(calcNumBytesTotal(o.mMask + 1)))); + detail::assertNotNull(std::malloc(numBytesTotal))); // no need for calloc because clonData does memcpy - mInfo = reinterpret_cast(mKeyVals + o.mMask + 1); + mInfo = reinterpret_cast(mKeyVals + numElementsWithBuffer); mNumElements = o.mNumElements; mMask = o.mMask; mMaxNumElementsAllowed = o.mMaxNumElementsAllowed; @@ -1384,15 +1607,17 @@ class unordered_map } // Creates a copy of the given map. Copy constructor of each entry is used. - unordered_map& operator=(unordered_map const& o) { - ROBIN_HOOD_TRACE(this); + // Not sure why clang-tidy thinks this doesn't handle self assignment, it does + // NOLINTNEXTLINE(bugprone-unhandled-self-assignment,cert-oop54-cpp) + Table& operator=(Table const& o) { + ROBIN_HOOD_TRACE(this) if (&o == this) { // prevent assigning of itself return *this; } - // we keep using the old allocator and not assign the new one, because we want to keep the - // memory available. when it is the same size. + // we keep using the old allocator and not assign the new one, because we want to keep + // the memory available. when it is the same size. if (o.empty()) { if (0 == mMask) { // nothing to do, we are empty too @@ -1403,33 +1628,39 @@ class unordered_map // clear also resets mInfo to 0, that's sometimes not necessary. destroy(); init(); - Hash::operator=(static_cast(o)); - KeyEqual::operator=(static_cast(o)); + WHash::operator=(static_cast(o)); + WKeyEqual::operator=(static_cast(o)); DataPool::operator=(static_cast(o)); return *this; } // clean up old stuff - Destroyer::value>{}.nodes(*this); + Destroyer::value>{}.nodes(*this); if (mMask != o.mMask) { // no luck: we don't have the same array size allocated, so we need to realloc. if (0 != mMask) { // only deallocate if we actually have data! - free(mKeyVals); + ROBIN_HOOD_LOG("std::free") + std::free(mKeyVals); } + auto const numElementsWithBuffer = calcNumElementsWithBuffer(o.mMask + 1); + auto const numBytesTotal = calcNumBytesTotal(numElementsWithBuffer); + ROBIN_HOOD_LOG("std::malloc " << numBytesTotal << " = calcNumBytesTotal(" + << numElementsWithBuffer << ")") mKeyVals = static_cast( - detail::assertNotNull(malloc(calcNumBytesTotal(o.mMask + 1)))); + detail::assertNotNull(std::malloc(numBytesTotal))); // no need for calloc here because cloneData performs a memcpy. - mInfo = reinterpret_cast(mKeyVals + o.mMask + 1); + mInfo = reinterpret_cast(mKeyVals + numElementsWithBuffer); // sentinel is set in cloneData } - Hash::operator=(static_cast(o)); - KeyEqual::operator=(static_cast(o)); + WHash::operator=(static_cast(o)); + WKeyEqual::operator=(static_cast(o)); DataPool::operator=(static_cast(o)); + mHashMultiplier = o.mHashMultiplier; mNumElements = o.mNumElements; mMask = o.mMask; mMaxNumElementsAllowed = o.mMaxNumElementsAllowed; @@ -1441,47 +1672,47 @@ class unordered_map } // Swaps everything between the two maps. - void swap(unordered_map& o) { - ROBIN_HOOD_TRACE(this); + void swap(Table& o) { + ROBIN_HOOD_TRACE(this) using std::swap; swap(o, *this); } // Clears all data, without resizing. void clear() { - ROBIN_HOOD_TRACE(this); + ROBIN_HOOD_TRACE(this) if (empty()) { - // don't do anything! also important because we don't want to write to DummyInfoByte::b, - // even though we would just write 0 to it. + // don't do anything! also important because we don't want to write to + // DummyInfoByte::b, even though we would just write 0 to it. return; } - Destroyer::value>{}.nodes(*this); + Destroyer::value>{}.nodes(*this); - // clear everything except the sentinel - // std::memset(mInfo, 0, sizeof(uint8_t) * (mMask + 1)); + auto const numElementsWithBuffer = calcNumElementsWithBuffer(mMask + 1); + // clear everything, then set the sentinel again uint8_t const z = 0; - std::fill(mInfo, mInfo + (sizeof(uint8_t) * (mMask + 1)), z); + std::fill(mInfo, mInfo + calcNumBytesInfo(numElementsWithBuffer), z); + mInfo[numElementsWithBuffer] = 1; mInfoInc = InitialInfoInc; mInfoHashShift = InitialInfoHashShift; } // Destroys the map and all it's contents. - ~unordered_map() { - ROBIN_HOOD_TRACE(this); + ~Table() { + ROBIN_HOOD_TRACE(this) destroy(); } - // Checks if both maps contain the same entries. Order is irrelevant. - bool operator==(const unordered_map& other) const { - ROBIN_HOOD_TRACE(this); + // Checks if both tables contain the same entries. Order is irrelevant. + bool operator==(const Table& other) const { + ROBIN_HOOD_TRACE(this) if (other.size() != size()) { return false; } for (auto const& otherEntry : other) { - auto const myIt = find(otherEntry.first); - if (myIt == end() || !(myIt->second == otherEntry.second)) { + if (!has(otherEntry)) { return false; } } @@ -1489,19 +1720,62 @@ class unordered_map return true; } - bool operator!=(const unordered_map& other) const { - ROBIN_HOOD_TRACE(this); + bool operator!=(const Table& other) const { + ROBIN_HOOD_TRACE(this) return !operator==(other); } - mapped_type& operator[](const key_type& key) { - ROBIN_HOOD_TRACE(this); - return doCreateByKey(key); + template + typename std::enable_if::value, Q&>::type operator[](const key_type& key) { + ROBIN_HOOD_TRACE(this) + auto idxAndState = insertKeyPrepareEmptySpot(key); + switch (idxAndState.second) { + case InsertionState::key_found: + break; + + case InsertionState::new_node: + ::new (static_cast(&mKeyVals[idxAndState.first])) + Node(*this, std::piecewise_construct, std::forward_as_tuple(key), + std::forward_as_tuple()); + break; + + case InsertionState::overwrite_node: + mKeyVals[idxAndState.first] = Node(*this, std::piecewise_construct, + std::forward_as_tuple(key), std::forward_as_tuple()); + break; + + case InsertionState::overflow_error: + throwOverflowError(); + } + + return mKeyVals[idxAndState.first].getSecond(); } - mapped_type& operator[](key_type&& key) { - ROBIN_HOOD_TRACE(this); - return doCreateByKey(std::move(key)); + template + typename std::enable_if::value, Q&>::type operator[](key_type&& key) { + ROBIN_HOOD_TRACE(this) + auto idxAndState = insertKeyPrepareEmptySpot(key); + switch (idxAndState.second) { + case InsertionState::key_found: + break; + + case InsertionState::new_node: + ::new (static_cast(&mKeyVals[idxAndState.first])) + Node(*this, std::piecewise_construct, std::forward_as_tuple(std::move(key)), + std::forward_as_tuple()); + break; + + case InsertionState::overwrite_node: + mKeyVals[idxAndState.first] = + Node(*this, std::piecewise_construct, std::forward_as_tuple(std::move(key)), + std::forward_as_tuple()); + break; + + case InsertionState::overflow_error: + throwOverflowError(); + } + + return mKeyVals[idxAndState.first].getSecond(); } template @@ -1512,31 +1786,98 @@ class unordered_map } } + void insert(std::initializer_list ilist) { + for (auto&& vt : ilist) { + insert(std::move(vt)); + } + } + template std::pair emplace(Args&&... args) { - ROBIN_HOOD_TRACE(this); + ROBIN_HOOD_TRACE(this) Node n{*this, std::forward(args)...}; - auto r = doInsert(std::move(n)); - if (!r.second) { - // insertion not possible: destroy node - // NOLINTNEXTLINE(bugprone-use-after-move) + auto idxAndState = insertKeyPrepareEmptySpot(getFirstConst(n)); + switch (idxAndState.second) { + case InsertionState::key_found: + n.destroy(*this); + break; + + case InsertionState::new_node: + ::new (static_cast(&mKeyVals[idxAndState.first])) Node(*this, std::move(n)); + break; + + case InsertionState::overwrite_node: + mKeyVals[idxAndState.first] = std::move(n); + break; + + case InsertionState::overflow_error: n.destroy(*this); + throwOverflowError(); + break; } - return r; + + return std::make_pair(iterator(mKeyVals + idxAndState.first, mInfo + idxAndState.first), + InsertionState::key_found != idxAndState.second); + } + + template + std::pair try_emplace(const key_type& key, Args&&... args) { + return try_emplace_impl(key, std::forward(args)...); + } + + template + std::pair try_emplace(key_type&& key, Args&&... args) { + return try_emplace_impl(std::move(key), std::forward(args)...); + } + + template + std::pair try_emplace(const_iterator hint, const key_type& key, + Args&&... args) { + (void)hint; + return try_emplace_impl(key, std::forward(args)...); + } + + template + std::pair try_emplace(const_iterator hint, key_type&& key, Args&&... args) { + (void)hint; + return try_emplace_impl(std::move(key), std::forward(args)...); + } + + template + std::pair insert_or_assign(const key_type& key, Mapped&& obj) { + return insertOrAssignImpl(key, std::forward(obj)); + } + + template + std::pair insert_or_assign(key_type&& key, Mapped&& obj) { + return insertOrAssignImpl(std::move(key), std::forward(obj)); + } + + template + std::pair insert_or_assign(const_iterator hint, const key_type& key, + Mapped&& obj) { + (void)hint; + return insertOrAssignImpl(key, std::forward(obj)); + } + + template + std::pair insert_or_assign(const_iterator hint, key_type&& key, Mapped&& obj) { + (void)hint; + return insertOrAssignImpl(std::move(key), std::forward(obj)); } std::pair insert(const value_type& keyval) { - ROBIN_HOOD_TRACE(this); - return doInsert(keyval); + ROBIN_HOOD_TRACE(this) + return emplace(keyval); } std::pair insert(value_type&& keyval) { - return doInsert(std::move(keyval)); + return emplace(std::move(keyval)); } // Returns 1 if key is found, 0 otherwise. size_t count(const key_type& key) const { // NOLINT(modernize-use-nodiscard) - ROBIN_HOOD_TRACE(this); + ROBIN_HOOD_TRACE(this) auto kv = mKeyVals + findIdx(key); if (kv != reinterpret_cast_no_cast_align_warning(mInfo)) { return 1; @@ -1544,10 +1885,33 @@ class unordered_map return 0; } + template + // NOLINTNEXTLINE(modernize-use-nodiscard) + typename std::enable_if::type count(const OtherKey& key) const { + ROBIN_HOOD_TRACE(this) + auto kv = mKeyVals + findIdx(key); + if (kv != reinterpret_cast_no_cast_align_warning(mInfo)) { + return 1; + } + return 0; + } + + bool contains(const key_type& key) const { // NOLINT(modernize-use-nodiscard) + return 1U == count(key); + } + + template + // NOLINTNEXTLINE(modernize-use-nodiscard) + typename std::enable_if::type contains(const OtherKey& key) const { + return 1U == count(key); + } + // Returns a reference to the value found for key. // Throws std::out_of_range if element cannot be found - mapped_type& at(key_type const& key) { - ROBIN_HOOD_TRACE(this); + template + // NOLINTNEXTLINE(modernize-use-nodiscard) + typename std::enable_if::value, Q&>::type at(key_type const& key) { + ROBIN_HOOD_TRACE(this) auto kv = mKeyVals + findIdx(key); if (kv == reinterpret_cast_no_cast_align_warning(mInfo)) { doThrow("key not found"); @@ -1557,8 +1921,10 @@ class unordered_map // Returns a reference to the value found for key. // Throws std::out_of_range if element cannot be found - mapped_type const& at(key_type const& key) const { // NOLINT(modernize-use-nodiscard) - ROBIN_HOOD_TRACE(this); + template + // NOLINTNEXTLINE(modernize-use-nodiscard) + typename std::enable_if::value, Q const&>::type at(key_type const& key) const { + ROBIN_HOOD_TRACE(this) auto kv = mKeyVals + findIdx(key); if (kv == reinterpret_cast_no_cast_align_warning(mInfo)) { doThrow("key not found"); @@ -1567,44 +1933,60 @@ class unordered_map } const_iterator find(const key_type& key) const { // NOLINT(modernize-use-nodiscard) - ROBIN_HOOD_TRACE(this); + ROBIN_HOOD_TRACE(this) const size_t idx = findIdx(key); return const_iterator{mKeyVals + idx, mInfo + idx}; } template const_iterator find(const OtherKey& key, is_transparent_tag /*unused*/) const { - ROBIN_HOOD_TRACE(this); + ROBIN_HOOD_TRACE(this) + const size_t idx = findIdx(key); + return const_iterator{mKeyVals + idx, mInfo + idx}; + } + + template + typename std::enable_if::type // NOLINT(modernize-use-nodiscard) + find(const OtherKey& key) const { // NOLINT(modernize-use-nodiscard) + ROBIN_HOOD_TRACE(this) const size_t idx = findIdx(key); return const_iterator{mKeyVals + idx, mInfo + idx}; } iterator find(const key_type& key) { - ROBIN_HOOD_TRACE(this); + ROBIN_HOOD_TRACE(this) const size_t idx = findIdx(key); return iterator{mKeyVals + idx, mInfo + idx}; } template iterator find(const OtherKey& key, is_transparent_tag /*unused*/) { - ROBIN_HOOD_TRACE(this); + ROBIN_HOOD_TRACE(this) + const size_t idx = findIdx(key); + return iterator{mKeyVals + idx, mInfo + idx}; + } + + template + typename std::enable_if::type find(const OtherKey& key) { + ROBIN_HOOD_TRACE(this) const size_t idx = findIdx(key); return iterator{mKeyVals + idx, mInfo + idx}; } iterator begin() { - ROBIN_HOOD_TRACE(this); + ROBIN_HOOD_TRACE(this) if (empty()) { return end(); } return iterator(mKeyVals, mInfo, fast_forward_tag{}); } const_iterator begin() const { // NOLINT(modernize-use-nodiscard) - ROBIN_HOOD_TRACE(this); + ROBIN_HOOD_TRACE(this) return cbegin(); } const_iterator cbegin() const { // NOLINT(modernize-use-nodiscard) - ROBIN_HOOD_TRACE(this); + ROBIN_HOOD_TRACE(this) if (empty()) { return cend(); } @@ -1612,22 +1994,22 @@ class unordered_map } iterator end() { - ROBIN_HOOD_TRACE(this); + ROBIN_HOOD_TRACE(this) // no need to supply valid info pointer: end() must not be dereferenced, and only node // pointer is compared. return iterator{reinterpret_cast_no_cast_align_warning(mInfo), nullptr}; } const_iterator end() const { // NOLINT(modernize-use-nodiscard) - ROBIN_HOOD_TRACE(this); + ROBIN_HOOD_TRACE(this) return cend(); } const_iterator cend() const { // NOLINT(modernize-use-nodiscard) - ROBIN_HOOD_TRACE(this); + ROBIN_HOOD_TRACE(this) return const_iterator{reinterpret_cast_no_cast_align_warning(mInfo), nullptr}; } iterator erase(const_iterator pos) { - ROBIN_HOOD_TRACE(this); + ROBIN_HOOD_TRACE(this) // its safe to perform const cast here // NOLINTNEXTLINE(cppcoreguidelines-pro-type-const-cast) return erase(iterator{const_cast(pos.mKeyVals), const_cast(pos.mInfo)}); @@ -1635,7 +2017,7 @@ class unordered_map // Erases element at pos, returns iterator to the next element. iterator erase(iterator pos) { - ROBIN_HOOD_TRACE(this); + ROBIN_HOOD_TRACE(this) // we assume that pos always points to a valid entry, and not end(). auto const idx = static_cast(pos.mKeyVals - mKeyVals); @@ -1652,14 +2034,14 @@ class unordered_map } size_t erase(const key_type& key) { - ROBIN_HOOD_TRACE(this); - size_t idx; - InfoType info; + ROBIN_HOOD_TRACE(this) + size_t idx{}; + InfoType info{}; keyToIdx(key, &idx, &info); // check while info matches with the source idx do { - if (info == mInfo[idx] && KeyEqual::operator()(key, mKeyVals[idx].getFirst())) { + if (info == mInfo[idx] && WKeyEqual::operator()(key, mKeyVals[idx].getFirst())) { shiftDown(idx); --mNumElements; return 1; @@ -1674,53 +2056,66 @@ class unordered_map // reserves space for the specified number of elements. Makes sure the old data fits. // exactly the same as reserve(c). void rehash(size_t c) { - reserve(c); + // forces a reserve + reserve(c, true); } // reserves space for the specified number of elements. Makes sure the old data fits. - // Exactly the same as resize(c). Use resize(0) to shrink to fit. + // Exactly the same as rehash(c). Use rehash(0) to shrink to fit. void reserve(size_t c) { - ROBIN_HOOD_TRACE(this); - auto const minElementsAllowed = (std::max)(c, mNumElements); + // reserve, but don't force rehash + reserve(c, false); + } + + // If possible reallocates the map to a smaller one. This frees the underlying table. + // Does not do anything if load_factor is too large for decreasing the table's size. + void compact() { + ROBIN_HOOD_TRACE(this) auto newSize = InitialNumElements; - while (calcMaxNumElementsAllowed(newSize) < minElementsAllowed && newSize != 0) { + while (calcMaxNumElementsAllowed(newSize) < mNumElements && newSize != 0) { newSize *= 2; } if (ROBIN_HOOD_UNLIKELY(newSize == 0)) { throwOverflowError(); } - rehashPowerOfTwo(newSize); + ROBIN_HOOD_LOG("newSize > mMask + 1: " << newSize << " > " << mMask << " + 1") + + // only actually do anything when the new size is bigger than the old one. This prevents to + // continuously allocate for each reserve() call. + if (newSize < mMask + 1) { + rehashPowerOfTwo(newSize, true); + } } size_type size() const noexcept { // NOLINT(modernize-use-nodiscard) - ROBIN_HOOD_TRACE(this); + ROBIN_HOOD_TRACE(this) return mNumElements; } size_type max_size() const noexcept { // NOLINT(modernize-use-nodiscard) - ROBIN_HOOD_TRACE(this); + ROBIN_HOOD_TRACE(this) return static_cast(-1); } ROBIN_HOOD(NODISCARD) bool empty() const noexcept { - ROBIN_HOOD_TRACE(this); + ROBIN_HOOD_TRACE(this) return 0 == mNumElements; } float max_load_factor() const noexcept { // NOLINT(modernize-use-nodiscard) - ROBIN_HOOD_TRACE(this); + ROBIN_HOOD_TRACE(this) return MaxLoadFactor100 / 100.0F; } // Average number of elements per bucket. Since we allow only 1 per bucket float load_factor() const noexcept { // NOLINT(modernize-use-nodiscard) - ROBIN_HOOD_TRACE(this); + ROBIN_HOOD_TRACE(this) return static_cast(size()) / static_cast(mMask + 1); } ROBIN_HOOD(NODISCARD) size_t mask() const noexcept { - ROBIN_HOOD_TRACE(this); + ROBIN_HOOD_TRACE(this) return mMask; } @@ -1733,11 +2128,19 @@ class unordered_map return (maxElements / 100) * MaxLoadFactor100; } - ROBIN_HOOD(NODISCARD) size_t calcNumBytesInfo(size_t numElements) const { + ROBIN_HOOD(NODISCARD) size_t calcNumBytesInfo(size_t numElements) const noexcept { + // we add a uint64_t, which houses the sentinel (first byte) and padding so we can load + // 64bit types. return numElements + sizeof(uint64_t); } - // calculation ony allowed for 2^n values + ROBIN_HOOD(NODISCARD) + size_t calcNumElementsWithBuffer(size_t numElements) const noexcept { + auto maxNumElementsAllowed = calcMaxNumElementsAllowed(numElements); + return numElements + (std::min)(maxNumElementsAllowed, (static_cast(0xFF))); + } + + // calculation only allowed for 2^n values ROBIN_HOOD(NODISCARD) size_t calcNumBytesTotal(size_t numElements) const { #if ROBIN_HOOD(BITNESS) == 64 return numElements * sizeof(Node) + calcNumBytesInfo(numElements); @@ -1758,133 +2161,196 @@ class unordered_map } private: + template + ROBIN_HOOD(NODISCARD) + typename std::enable_if::value, bool>::type has(const value_type& e) const { + ROBIN_HOOD_TRACE(this) + auto it = find(e.first); + return it != end() && it->second == e.second; + } + + template + ROBIN_HOOD(NODISCARD) + typename std::enable_if::value, bool>::type has(const value_type& e) const { + ROBIN_HOOD_TRACE(this) + return find(e) != end(); + } + + void reserve(size_t c, bool forceRehash) { + ROBIN_HOOD_TRACE(this) + auto const minElementsAllowed = (std::max)(c, mNumElements); + auto newSize = InitialNumElements; + while (calcMaxNumElementsAllowed(newSize) < minElementsAllowed && newSize != 0) { + newSize *= 2; + } + if (ROBIN_HOOD_UNLIKELY(newSize == 0)) { + throwOverflowError(); + } + + ROBIN_HOOD_LOG("newSize > mMask + 1: " << newSize << " > " << mMask << " + 1") + + // only actually do anything when the new size is bigger than the old one. This prevents to + // continuously allocate for each reserve() call. + if (forceRehash || newSize > mMask + 1) { + rehashPowerOfTwo(newSize, false); + } + } + // reserves space for at least the specified number of elements. // only works if numBuckets if power of two - void rehashPowerOfTwo(size_t numBuckets) { - ROBIN_HOOD_TRACE(this); + // True on success, false otherwise + void rehashPowerOfTwo(size_t numBuckets, bool forceFree) { + ROBIN_HOOD_TRACE(this) Node* const oldKeyVals = mKeyVals; uint8_t const* const oldInfo = mInfo; - const size_t oldMaxElements = mMask + 1; + const size_t oldMaxElementsWithBuffer = calcNumElementsWithBuffer(mMask + 1); // resize operation: move stuff - init_data(numBuckets); - if (oldMaxElements > 1) { - for (size_t i = 0; i < oldMaxElements; ++i) { + initData(numBuckets); + if (oldMaxElementsWithBuffer > 1) { + for (size_t i = 0; i < oldMaxElementsWithBuffer; ++i) { if (oldInfo[i] != 0) { + // might throw an exception, which is really bad since we are in the middle of + // moving stuff. insert_move(std::move(oldKeyVals[i])); // destroy the node but DON'T destroy the data. oldKeyVals[i].~Node(); } } - // don't destroy old data: put it into the pool instead - DataPool::addOrFree(oldKeyVals, calcNumBytesTotal(oldMaxElements)); + // this check is not necessary as it's guarded by the previous if, but it helps + // silence g++'s overeager "attempt to free a non-heap object 'map' + // [-Werror=free-nonheap-object]" warning. + if (oldKeyVals != reinterpret_cast_no_cast_align_warning(&mMask)) { + // don't destroy old data: put it into the pool instead + if (forceFree) { + std::free(oldKeyVals); + } else { + DataPool::addOrFree(oldKeyVals, calcNumBytesTotal(oldMaxElementsWithBuffer)); + } + } } } ROBIN_HOOD(NOINLINE) void throwOverflowError() const { +#if ROBIN_HOOD(HAS_EXCEPTIONS) throw std::overflow_error("robin_hood::map overflow"); +#else + abort(); +#endif } - void init_data(size_t max_elements) { - mNumElements = 0; - mMask = max_elements - 1; - mMaxNumElementsAllowed = calcMaxNumElementsAllowed(max_elements); + template + std::pair try_emplace_impl(OtherKey&& key, Args&&... args) { + ROBIN_HOOD_TRACE(this) + auto idxAndState = insertKeyPrepareEmptySpot(key); + switch (idxAndState.second) { + case InsertionState::key_found: + break; - // calloc also zeroes everything - mKeyVals = reinterpret_cast( - detail::assertNotNull(calloc(1, calcNumBytesTotal(max_elements)))); - mInfo = reinterpret_cast(mKeyVals + max_elements); + case InsertionState::new_node: + ::new (static_cast(&mKeyVals[idxAndState.first])) Node( + *this, std::piecewise_construct, std::forward_as_tuple(std::forward(key)), + std::forward_as_tuple(std::forward(args)...)); + break; - // set sentinel - mInfo[max_elements] = 1; + case InsertionState::overwrite_node: + mKeyVals[idxAndState.first] = Node(*this, std::piecewise_construct, + std::forward_as_tuple(std::forward(key)), + std::forward_as_tuple(std::forward(args)...)); + break; - mInfoInc = InitialInfoInc; - mInfoHashShift = InitialInfoHashShift; + case InsertionState::overflow_error: + throwOverflowError(); + break; + } + + return std::make_pair(iterator(mKeyVals + idxAndState.first, mInfo + idxAndState.first), + InsertionState::key_found != idxAndState.second); } - template - mapped_type& doCreateByKey(Arg&& key) { - while (true) { - size_t idx; - InfoType info; - keyToIdx(key, &idx, &info); - nextWhileLess(&info, &idx); + template + std::pair insertOrAssignImpl(OtherKey&& key, Mapped&& obj) { + ROBIN_HOOD_TRACE(this) + auto idxAndState = insertKeyPrepareEmptySpot(key); + switch (idxAndState.second) { + case InsertionState::key_found: + mKeyVals[idxAndState.first].getSecond() = std::forward(obj); + break; - // while we potentially have a match. Can't do a do-while here because when mInfo is 0 - // we don't want to skip forward - while (info == mInfo[idx]) { - if (KeyEqual::operator()(key, mKeyVals[idx].getFirst())) { - // key already exists, do not insert. - return mKeyVals[idx].getSecond(); - } - next(&info, &idx); - } + case InsertionState::new_node: + ::new (static_cast(&mKeyVals[idxAndState.first])) Node( + *this, std::piecewise_construct, std::forward_as_tuple(std::forward(key)), + std::forward_as_tuple(std::forward(obj))); + break; - // unlikely that this evaluates to true - if (ROBIN_HOOD_UNLIKELY(mNumElements >= mMaxNumElementsAllowed)) { - increase_size(); - continue; - } + case InsertionState::overwrite_node: + mKeyVals[idxAndState.first] = Node(*this, std::piecewise_construct, + std::forward_as_tuple(std::forward(key)), + std::forward_as_tuple(std::forward(obj))); + break; - // key not found, so we are now exactly where we want to insert it. - auto const insertion_idx = idx; - auto const insertion_info = info; - if (ROBIN_HOOD_UNLIKELY(insertion_info + mInfoInc > 0xFF)) { - mMaxNumElementsAllowed = 0; - } + case InsertionState::overflow_error: + throwOverflowError(); + break; + } - // find an empty spot - while (0 != mInfo[idx]) { - next(&info, &idx); - } + return std::make_pair(iterator(mKeyVals + idxAndState.first, mInfo + idxAndState.first), + InsertionState::key_found != idxAndState.second); + } - auto& l = mKeyVals[insertion_idx]; - if (idx == insertion_idx) { - // put at empty spot. This forwards all arguments into the node where the object is - // constructed exactly where it is needed. - ::new (static_cast(&l)) - Node(*this, std::piecewise_construct, - std::forward_as_tuple(std::forward(key)), std::forward_as_tuple()); - } else { - shiftUp(idx, insertion_idx); - l = Node(*this, std::piecewise_construct, - std::forward_as_tuple(std::forward(key)), std::forward_as_tuple()); - } + void initData(size_t max_elements) { + mNumElements = 0; + mMask = max_elements - 1; + mMaxNumElementsAllowed = calcMaxNumElementsAllowed(max_elements); - // mKeyVals[idx].getFirst() = std::move(key); - mInfo[insertion_idx] = static_cast(insertion_info); + auto const numElementsWithBuffer = calcNumElementsWithBuffer(max_elements); - ++mNumElements; - return mKeyVals[insertion_idx].getSecond(); - } + // calloc also zeroes everything + auto const numBytesTotal = calcNumBytesTotal(numElementsWithBuffer); + ROBIN_HOOD_LOG("std::calloc " << numBytesTotal << " = calcNumBytesTotal(" + << numElementsWithBuffer << ")") + mKeyVals = reinterpret_cast( + detail::assertNotNull(std::calloc(1, numBytesTotal))); + mInfo = reinterpret_cast(mKeyVals + numElementsWithBuffer); + + // set sentinel + mInfo[numElementsWithBuffer] = 1; + + mInfoInc = InitialInfoInc; + mInfoHashShift = InitialInfoHashShift; } - // This is exactly the same code as operator[], except for the return values - template - std::pair doInsert(Arg&& keyval) { - while (true) { - size_t idx; - InfoType info; - keyToIdx(keyval.getFirst(), &idx, &info); + enum class InsertionState { overflow_error, key_found, new_node, overwrite_node }; + + // Finds key, and if not already present prepares a spot where to pot the key & value. + // This potentially shifts nodes out of the way, updates mInfo and number of inserted + // elements, so the only operation left to do is create/assign a new node at that spot. + template + std::pair insertKeyPrepareEmptySpot(OtherKey&& key) { + for (int i = 0; i < 256; ++i) { + size_t idx{}; + InfoType info{}; + keyToIdx(key, &idx, &info); nextWhileLess(&info, &idx); // while we potentially have a match while (info == mInfo[idx]) { - if (KeyEqual::operator()(keyval.getFirst(), mKeyVals[idx].getFirst())) { + if (WKeyEqual::operator()(key, mKeyVals[idx].getFirst())) { // key already exists, do NOT insert. // see http://en.cppreference.com/w/cpp/container/unordered_map/insert - return std::make_pair(iterator(mKeyVals + idx, mInfo + idx), - false); + return std::make_pair(idx, InsertionState::key_found); } next(&info, &idx); } // unlikely that this evaluates to true if (ROBIN_HOOD_UNLIKELY(mNumElements >= mMaxNumElementsAllowed)) { - increase_size(); + if (!increase_size()) { + return std::make_pair(size_t(0), InsertionState::overflow_error); + } continue; } @@ -1900,26 +2366,25 @@ class unordered_map next(&info, &idx); } - auto& l = mKeyVals[insertion_idx]; - if (idx == insertion_idx) { - ::new (static_cast(&l)) Node(*this, std::forward(keyval)); - } else { + if (idx != insertion_idx) { shiftUp(idx, insertion_idx); - l = Node(*this, std::forward(keyval)); } - // put at empty spot mInfo[insertion_idx] = static_cast(insertion_info); - ++mNumElements; - return std::make_pair(iterator(mKeyVals + insertion_idx, mInfo + insertion_idx), true); + return std::make_pair(insertion_idx, idx == insertion_idx + ? InsertionState::new_node + : InsertionState::overwrite_node); } + + // enough attempts failed, so finally give up. + return std::make_pair(size_t(0), InsertionState::overflow_error); } bool try_increase_info() { ROBIN_HOOD_LOG("mInfoInc=" << mInfoInc << ", numElements=" << mNumElements << ", maxNumElementsAllowed=" - << calcMaxNumElementsAllowed(mMask + 1)); + << calcMaxNumElementsAllowed(mMask + 1)) if (mInfoInc <= 2) { // need to be > 2 so that shift works (otherwise undefined behavior!) return false; @@ -1930,38 +2395,55 @@ class unordered_map // remove one bit of the hash, leaving more space for the distance info. // This is extremely fast because we can operate on 8 bytes at once. ++mInfoHashShift; - auto const data = reinterpret_cast_no_cast_align_warning(mInfo); - auto const numEntries = (mMask + 1) / 8; + auto const numElementsWithBuffer = calcNumElementsWithBuffer(mMask + 1); - for (size_t i = 0; i < numEntries; ++i) { - data[i] = (data[i] >> 1U) & UINT64_C(0x7f7f7f7f7f7f7f7f); + for (size_t i = 0; i < numElementsWithBuffer; i += 8) { + auto val = unaligned_load(mInfo + i); + val = (val >> 1U) & UINT64_C(0x7f7f7f7f7f7f7f7f); + std::memcpy(mInfo + i, &val, sizeof(val)); } + // update sentinel, which might have been cleared out! + mInfo[numElementsWithBuffer] = 1; + mMaxNumElementsAllowed = calcMaxNumElementsAllowed(mMask + 1); return true; } - void increase_size() { + // True if resize was possible, false otherwise + bool increase_size() { // nothing allocated yet? just allocate InitialNumElements if (0 == mMask) { - init_data(InitialNumElements); - return; + initData(InitialNumElements); + return true; } auto const maxNumElementsAllowed = calcMaxNumElementsAllowed(mMask + 1); if (mNumElements < maxNumElementsAllowed && try_increase_info()) { - return; + return true; } ROBIN_HOOD_LOG("mNumElements=" << mNumElements << ", maxNumElementsAllowed=" << maxNumElementsAllowed << ", load=" << (static_cast(mNumElements) * 100.0 / - (static_cast(mMask) + 1))); - // it seems we have a really bad hash function! don't try to resize again + (static_cast(mMask) + 1))) + if (mNumElements * 2 < calcMaxNumElementsAllowed(mMask + 1)) { - throwOverflowError(); + // we have to resize, even though there would still be plenty of space left! + // Try to rehash instead. Delete freed memory so we don't steadyily increase mem in case + // we have to rehash a few times + nextHashMultiplier(); + rehashPowerOfTwo(mMask + 1, true); + } else { + // we've reached the capacity of the map, so the hash seems to work nice. Keep using it. + rehashPowerOfTwo((mMask + 1) * 2, false); } + return true; + } - rehashPowerOfTwo((mMask + 1) * 2); + void nextHashMultiplier() { + // adding an *even* number, so that the multiplier will always stay odd. This is necessary + // so that the hash stays a mixing function (and thus doesn't have any information loss). + mHashMultiplier += UINT64_C(0xc4ceb9fe1a85ec54); } void destroy() { @@ -1970,13 +2452,21 @@ class unordered_map return; } - Destroyer::value>{} + Destroyer::value>{} .nodesDoNotDeallocate(*this); - free(mKeyVals); + + // This protection against not deleting mMask shouldn't be needed as it's sufficiently + // protected with the 0==mMask check, but I have this anyways because g++ 7 otherwise + // reports a compile error: attempt to free a non-heap object 'fm' + // [-Werror=free-nonheap-object] + if (mKeyVals != reinterpret_cast_no_cast_align_warning(&mMask)) { + ROBIN_HOOD_LOG("std::free") + std::free(mKeyVals); + } } void init() noexcept { - mKeyVals = reinterpret_cast(&mMask); + mKeyVals = reinterpret_cast_no_cast_align_warning(&mMask); mInfo = reinterpret_cast(&mMask); mNumElements = 0; mMask = 0; @@ -1986,33 +2476,53 @@ class unordered_map } // members are sorted so no padding occurs - Node* mKeyVals = reinterpret_cast(&mMask); // 8 byte 8 - uint8_t* mInfo = reinterpret_cast(&mMask); // 8 byte 16 - size_t mNumElements = 0; // 8 byte 24 - size_t mMask = 0; // 8 byte 32 - size_t mMaxNumElementsAllowed = 0; // 8 byte 40 - InfoType mInfoInc = InitialInfoInc; // 4 byte 44 - InfoType mInfoHashShift = InitialInfoHashShift; // 4 byte 48 - // 16 byte 56 if NodeAllocator + uint64_t mHashMultiplier = UINT64_C(0xc4ceb9fe1a85ec53); // 8 byte 8 + Node* mKeyVals = reinterpret_cast_no_cast_align_warning(&mMask); // 8 byte 16 + uint8_t* mInfo = reinterpret_cast(&mMask); // 8 byte 24 + size_t mNumElements = 0; // 8 byte 32 + size_t mMask = 0; // 8 byte 40 + size_t mMaxNumElementsAllowed = 0; // 8 byte 48 + InfoType mInfoInc = InitialInfoInc; // 4 byte 52 + InfoType mInfoHashShift = InitialInfoHashShift; // 4 byte 56 + // 16 byte 56 if NodeAllocator }; } // namespace detail +// map + template , typename KeyEqual = std::equal_to, size_t MaxLoadFactor100 = 80> -using unordered_flat_map = detail::unordered_map; +using unordered_flat_map = detail::Table; template , typename KeyEqual = std::equal_to, size_t MaxLoadFactor100 = 80> -using unordered_node_map = detail::unordered_map; +using unordered_node_map = detail::Table; template , typename KeyEqual = std::equal_to, size_t MaxLoadFactor100 = 80> using unordered_map = - detail::unordered_map) <= sizeof(size_t) * 6 && - std::is_nothrow_move_constructible>::value && - std::is_nothrow_move_assignable>::value, - MaxLoadFactor100, Key, T, Hash, KeyEqual>; + detail::Table) <= sizeof(size_t) * 6 && + std::is_nothrow_move_constructible>::value && + std::is_nothrow_move_assignable>::value, + MaxLoadFactor100, Key, T, Hash, KeyEqual>; + +// set + +template , typename KeyEqual = std::equal_to, + size_t MaxLoadFactor100 = 80> +using unordered_flat_set = detail::Table; + +template , typename KeyEqual = std::equal_to, + size_t MaxLoadFactor100 = 80> +using unordered_node_set = detail::Table; + +template , typename KeyEqual = std::equal_to, + size_t MaxLoadFactor100 = 80> +using unordered_set = detail::Table::value && + std::is_nothrow_move_assignable::value, + MaxLoadFactor100, Key, void, Hash, KeyEqual>; } // namespace robin_hood diff --git a/src/statements.cpp b/src/statements.cpp index 98e8f39a..25bc11cc 100644 --- a/src/statements.cpp +++ b/src/statements.cpp @@ -165,7 +165,7 @@ void ForStatement::RenderLoop(const InternalValue& loopVal, OutStream& os, Rende ListAdapter ForStatement::CreateFilteredAdapter(const ListAdapter& loopItems, RenderContext& values) const { - return ListAdapter::CreateAdapter([e = loopItems.GetEnumerator(), this, &values]() { + return ListAdapter::CreateAdapter([e = loopItems.GetEnumerator(), this, &values]() mutable { using ResultType = nonstd::optional; auto& tempContext = values.EnterScope(); @@ -268,7 +268,7 @@ void SetFilteredBlockStatement::Render(OutStream&, RenderContext& values) AssignBody(m_expr->Evaluate(RenderBody(values), values), values); } -class BlocksRenderer : public RendererBase +class IBlocksRenderer : public IRendererBase { public: virtual bool HasBlock(const std::string& blockName) = 0; @@ -291,14 +291,14 @@ void ParentBlockStatement::Render(OutStream& os, RenderContext& values) if (!isConverted) return; - BlocksRenderer* blockRenderer = nullptr; // static_cast(*parentTplPtr); + IBlocksRenderer* blockRenderer = nullptr; // static_cast(*parentTplPtr); for (auto& tplVal : parentTplsList) { auto ptr = GetIf(&tplVal); if (!ptr) continue; - auto parentTplPtr = static_cast(ptr->get()); + auto parentTplPtr = static_cast(ptr->get()); if (parentTplPtr->HasBlock(m_name)) { @@ -337,7 +337,7 @@ void BlockStatement::Render(OutStream& os, RenderContext& values) } template -class ParentTemplateRenderer : public BlocksRenderer +class ParentTemplateRenderer : public IBlocksRenderer { public: ParentTemplateRenderer(std::shared_ptr> tpl, ExtendsStatement::BlocksCollection* blocks) @@ -378,6 +378,20 @@ class ParentTemplateRenderer : public BlocksRenderer bool HasBlock(const std::string& blockName) override { return m_blocks->count(blockName) != 0; } + bool IsEqual(const IComparable& other) const override + { + auto* val = dynamic_cast(&other); + if (!val) + return false; + if (m_template != val->m_template) + return false; + if (m_blocks && val->m_blocks && *m_blocks != *(val->m_blocks)) + return false; + if ((m_blocks && !val->m_blocks) || (!m_blocks && val->m_blocks)) + return false; + return true; + } + private: std::shared_ptr> m_template; ExtendsStatement::BlocksCollection* m_blocks; @@ -388,7 +402,7 @@ struct TemplateImplVisitor { // ExtendsStatement::BlocksCollection* m_blocks; const Fn& m_fn; - bool m_throwError; + bool m_throwError{}; explicit TemplateImplVisitor(const Fn& fn, bool throwError) : m_fn(fn) @@ -440,7 +454,7 @@ void ExtendsStatement::Render(OutStream& os, RenderContext& values) } template -class IncludedTemplateRenderer : public RendererBase +class IncludedTemplateRenderer : public IRendererBase { public: IncludedTemplateRenderer(std::shared_ptr> tpl, bool withContext) @@ -467,9 +481,21 @@ class IncludedTemplateRenderer : public RendererBase } } + bool IsEqual(const IComparable& other) const override + { + auto* val = dynamic_cast*>(&other); + if (!val) + return false; + if (m_template != val->m_template) + return false; + if (m_withContext != val->m_withContext) + return false; + return true; + } + private: std::shared_ptr> m_template; - bool m_withContext; + bool m_withContext{}; }; void IncludeStatement::Render(OutStream& os, RenderContext& values) @@ -539,7 +565,7 @@ void IncludeStatement::Render(OutStream& os, RenderContext& values) } } -class ImportedMacroRenderer : public RendererBase +class ImportedMacroRenderer : public IRendererBase { public: explicit ImportedMacroRenderer(InternalValueMap&& map, bool withContext) @@ -572,9 +598,21 @@ class ImportedMacroRenderer : public RendererBase renderer->InvokeMacro(callable, params, stream, context); } + bool IsEqual(const IComparable& other) const override + { + auto* val = dynamic_cast(&other); + if (!val) + return false; + if (m_importedContext != val->m_importedContext) + return false; + if (m_withContext != val->m_withContext) + return false; + return true; + } + private: InternalValueMap m_importedContext; - bool m_withContext; + bool m_withContext{}; }; void ImportStatement::Render(OutStream& /*os*/, RenderContext& values) @@ -609,7 +647,7 @@ void ImportStatement::Render(OutStream& /*os*/, RenderContext& values) ImportNames(values, importedScope, scopeName); values.GetCurrentScope()[scopeName] = - std::static_pointer_cast(std::make_shared(std::move(importedScope), m_withContext)); + std::static_pointer_cast(std::make_shared(std::move(importedScope), m_withContext)); } void ImportStatement::ImportNames(RenderContext& values, InternalValueMap& importedScope, const std::string& scopeName) const diff --git a/src/statements.h b/src/statements.h index 46140d89..eed6e2c0 100644 --- a/src/statements.h +++ b/src/statements.h @@ -26,6 +26,14 @@ struct MacroParam std::string paramName; ExpressionEvaluatorPtr<> defaultValue; }; +inline bool operator==(const MacroParam& lhs, const MacroParam& rhs) +{ + if (lhs.paramName != rhs.paramName) + return false; + if (lhs.defaultValue != rhs.defaultValue) + return false; + return true; +} using MacroParams = std::vector; @@ -33,7 +41,7 @@ class ForStatement : public Statement { public: VISITABLE_STATEMENT(); - + ForStatement(std::vector vars, ExpressionEvaluatorPtr<> expr, ExpressionEvaluatorPtr<> ifExpr, bool isRecursive) : m_vars(std::move(vars)) , m_value(expr) @@ -54,6 +62,26 @@ class ForStatement : public Statement void Render(OutStream& os, RenderContext& values) override; + bool IsEqual(const IComparable& other) const override + { + auto* val = dynamic_cast(&other); + if (!val) + return false; + if (m_vars != val->m_vars) + return false; + if (m_value != val->m_value) + return false; + if (m_ifExpr != val->m_ifExpr) + return false; + if (m_isRecursive != val->m_isRecursive) + return false; + if (m_mainBody != val->m_mainBody) + return false; + if (m_elseBody != val->m_elseBody) + return false; + return true; + } + private: void RenderLoop(const InternalValue &loopVal, OutStream &os, RenderContext &values, int level); @@ -63,10 +91,9 @@ class ForStatement : public Statement std::vector m_vars; ExpressionEvaluatorPtr<> m_value; ExpressionEvaluatorPtr<> m_ifExpr; - bool m_isRecursive; + bool m_isRecursive{}; RendererPtr m_mainBody; RendererPtr m_elseBody; - }; class ElseBranchStatement; @@ -93,6 +120,19 @@ class IfStatement : public Statement void Render(OutStream& os, RenderContext& values) override; + bool IsEqual(const IComparable& other) const override + { + auto* val = dynamic_cast(&other); + if (!val) + return false; + if (m_expr != val->m_expr) + return false; + if (m_mainBody != val->m_mainBody) + return false; + if (m_elseBranches != val->m_elseBranches) + return false; + return true; + } private: ExpressionEvaluatorPtr<> m_expr; RendererPtr m_mainBody; @@ -116,6 +156,17 @@ class ElseBranchStatement : public Statement m_mainBody = std::move(renderer); } void Render(OutStream& os, RenderContext& values) override; + bool IsEqual(const IComparable& other) const override + { + auto* val = dynamic_cast(&other); + if (!val) + return false; + if (m_expr != val->m_expr) + return false; + if (m_mainBody != val->m_mainBody) + return false; + return true; + } private: ExpressionEvaluatorPtr<> m_expr; @@ -130,6 +181,15 @@ class SetStatement : public Statement { } + bool IsEqual(const IComparable& other) const override + { + auto* val = dynamic_cast(&other); + if (!val) + return false; + if (m_fields != val->m_fields) + return false; + return true; + } protected: void AssignBody(InternalValue, RenderContext&); @@ -149,6 +209,15 @@ class SetLineStatement final : public SetStatement void Render(OutStream& os, RenderContext& values) override; + bool IsEqual(const IComparable& other) const override + { + auto* val = dynamic_cast(&other); + if (!val) + return false; + if (m_expr != val->m_expr) + return false; + return true; + } private: const ExpressionEvaluatorPtr<> m_expr; }; @@ -163,6 +232,17 @@ class SetBlockStatement : public SetStatement m_body = std::move(renderer); } + bool IsEqual(const IComparable& other) const override + { + auto* val = dynamic_cast(&other); + if (!val) + return false; + if (!SetStatement::IsEqual(*val)) + return false; + if (m_body != val->m_body) + return false; + return true; + } protected: InternalValue RenderBody(RenderContext&); @@ -178,6 +258,16 @@ class SetRawBlockStatement final : public SetBlockStatement using SetBlockStatement::SetBlockStatement; void Render(OutStream&, RenderContext&) override; + + bool IsEqual(const IComparable& other) const override + { + auto* val = dynamic_cast(&other); + if (!val) + return false; + if (!SetBlockStatement::IsEqual(*val)) + return false; + return true; + } }; class SetFilteredBlockStatement final : public SetBlockStatement @@ -192,6 +282,18 @@ class SetFilteredBlockStatement final : public SetBlockStatement void Render(OutStream&, RenderContext&) override; + bool IsEqual(const IComparable& other) const override + { + auto* val = dynamic_cast(&other); + if (!val) + return false; + if (!SetBlockStatement::IsEqual(*val)) + return false; + if (m_expr != val->m_expr) + return false; + return true; + } + private: const ExpressionEvaluatorPtr m_expr; }; @@ -213,9 +315,23 @@ class ParentBlockStatement : public Statement } void Render(OutStream &os, RenderContext &values) override; + bool IsEqual(const IComparable& other) const override + { + auto* val = dynamic_cast(&other); + if (!val) + return false; + if (m_name != val->m_name) + return false; + if (m_isScoped != val->m_isScoped) + return false; + if (m_mainBody != val->m_mainBody) + return false; + return true; + } + private: std::string m_name; - bool m_isScoped; + bool m_isScoped{}; RendererPtr m_mainBody; }; @@ -237,6 +353,17 @@ class BlockStatement : public Statement } void Render(OutStream &os, RenderContext &values) override; + bool IsEqual(const IComparable& other) const override + { + auto* val = dynamic_cast(&other); + if (!val) + return false; + if (m_name != val->m_name) + return false; + if (m_mainBody != val->m_mainBody) + return false; + return true; + } private: std::string m_name; RendererPtr m_mainBody; @@ -260,9 +387,22 @@ class ExtendsStatement : public Statement { m_blocks[block->GetName()] = block; } + bool IsEqual(const IComparable& other) const override + { + auto* val = dynamic_cast(&other); + if (!val) + return false; + if (m_templateName != val->m_templateName) + return false; + if (m_isPath != val->m_isPath) + return false; + if (m_blocks != val->m_blocks) + return false; + return true; + } private: std::string m_templateName; - bool m_isPath; + bool m_isPath{}; BlocksCollection m_blocks; void DoRender(OutStream &os, RenderContext &values); }; @@ -283,9 +423,22 @@ class IncludeStatement : public Statement } void Render(OutStream& os, RenderContext& values) override; + bool IsEqual(const IComparable& other) const override + { + auto* val = dynamic_cast(&other); + if (!val) + return false; + if (m_ignoreMissing != val->m_ignoreMissing) + return false; + if (m_withContext != val->m_withContext) + return false; + if (m_expr != val->m_expr) + return false; + return true; + } private: - bool m_ignoreMissing; - bool m_withContext; + bool m_ignoreMissing{}; + bool m_withContext{}; ExpressionEvaluatorPtr<> m_expr; }; @@ -315,11 +468,28 @@ class ImportStatement : public Statement void Render(OutStream& os, RenderContext& values) override; + bool IsEqual(const IComparable& other) const override + { + auto* val = dynamic_cast(&other); + if (!val) + return false; + if (m_namespace != val->m_namespace) + return false; + if (m_withContext != val->m_withContext) + return false; + if (m_namesToImport != val->m_namesToImport) + return false; + if (m_nameExpr != val->m_nameExpr) + return false; + if (m_renderer != val->m_renderer) + return false; + return true; + } private: void ImportNames(RenderContext& values, InternalValueMap& importedScope, const std::string& scopeName) const; private: - bool m_withContext; + bool m_withContext{}; RendererPtr m_renderer; ExpressionEvaluatorPtr<> m_nameExpr; nonstd::optional m_namespace; @@ -344,6 +514,20 @@ class MacroStatement : public Statement void Render(OutStream &os, RenderContext &values) override; + bool IsEqual(const IComparable& other) const override + { + auto* val = dynamic_cast(&other); + if (!val) + return false; + if (m_name != val->m_name) + return false; + if (m_params != val->m_params) + return false; + if (m_mainBody != val->m_mainBody) + return false; + return true; + } + protected: void InvokeMacroRenderer(const std::vector& params, const CallParams& callParams, OutStream& stream, RenderContext& context); void SetupCallArgs(const std::vector& argsInfo, const CallParams& callParams, RenderContext& context, InternalValueMap& callArgs, InternalValueMap& kwArgs, InternalValueList& varArgs); @@ -370,6 +554,17 @@ class MacroCallStatement : public MacroStatement void Render(OutStream &os, RenderContext &values) override; + bool IsEqual(const IComparable& other) const override + { + auto* val = dynamic_cast(&other); + if (!val) + return false; + if (m_macroName != val->m_macroName) + return false; + if (m_callParams != val->m_callParams) + return false; + return true; + } protected: void SetupMacroScope(InternalValueMap& scope) override; @@ -386,7 +581,15 @@ class DoStatement : public Statement DoStatement(ExpressionEvaluatorPtr<> expr) : m_expr(expr) {} void Render(OutStream &os, RenderContext &values) override; - + bool IsEqual(const IComparable& other) const override + { + auto* val = dynamic_cast(&other); + if (!val) + return false; + if (m_expr != val->m_expr) + return false; + return true; + } private: ExpressionEvaluatorPtr<> m_expr; }; @@ -406,7 +609,17 @@ class WithStatement : public Statement } void Render(OutStream &os, RenderContext &values) override; - + bool IsEqual(const IComparable& other) const override + { + auto* val = dynamic_cast(&other); + if (!val) + return false; + if (m_scopeVars != val->m_scopeVars) + return false; + if (m_mainBody != val->m_mainBody) + return false; + return true; + } private: std::vector>> m_scopeVars; RendererPtr m_mainBody; @@ -424,14 +637,25 @@ class FilterStatement : public Statement { m_body = std::move(renderer); } - + void Render(OutStream &, RenderContext &) override; + bool IsEqual(const IComparable& other) const override + { + auto* val = dynamic_cast(&other); + if (!val) + return false; + if (m_expr != val->m_expr) + return false; + if (m_body != val->m_body) + return false; + return true; + } private: ExpressionEvaluatorPtr m_expr; RendererPtr m_body; }; -} // namespace jinja2 +} // namespace jinja2 #endif // JINJA2CPP_SRC_STATEMENTS_H diff --git a/src/template.cpp b/src/template.cpp index 8fbc3c5a..27343875 100644 --- a/src/template.cpp +++ b/src/template.cpp @@ -8,6 +8,15 @@ namespace jinja2 { +bool operator==(const Template& lhs, const Template& rhs) +{ + return lhs.IsEqual(rhs); +} + +bool operator==(const TemplateW& lhs, const TemplateW& rhs) +{ + return lhs.IsEqual(rhs); +} template auto GetImpl(std::shared_ptr impl) @@ -91,6 +100,11 @@ Result> Template::GetMetadataRaw() return GetImpl(m_impl)->GetMetadataRaw(); } +bool Template::IsEqual(const Template& other) const +{ + return m_impl == other.m_impl; +} + TemplateW::TemplateW(TemplateEnv* env) : m_impl(new TemplateImpl(env)) { @@ -168,4 +182,9 @@ ResultW> TemplateW::GetMetadataRaw() // GetImpl(m_impl)->GetMetadataRaw(); ; } +bool TemplateW::IsEqual(const TemplateW& other) const +{ + return m_impl == other.m_impl; +} + } // namespace jinja2 diff --git a/src/template_impl.h b/src/template_impl.h index 34040bb1..a84e60fe 100644 --- a/src/template_impl.h +++ b/src/template_impl.h @@ -157,7 +157,49 @@ struct ErrorConverter, ErrorInfoTpl> return srcError; } }; - + +template +inline bool operator==(const MetadataInfo& lhs, const MetadataInfo& rhs) +{ + if (lhs.metadata != rhs.metadata) + return false; + if (lhs.metadataType != rhs.metadataType) + return false; + if (lhs.location != rhs.location) + return false; + return true; +} + +template +inline bool operator!=(const MetadataInfo& lhs, const MetadataInfo& rhs) +{ + return !(lhs == rhs); +} + +inline bool operator==(const TemplateEnv& lhs, const TemplateEnv& rhs) +{ + return lhs.IsEqual(rhs); +} +inline bool operator!=(const TemplateEnv& lhs, const TemplateEnv& rhs) +{ + return !(lhs == rhs); +} + +inline bool operator==(const SourceLocation& lhs, const SourceLocation& rhs) +{ + if (lhs.fileName != rhs.fileName) + return false; + if (lhs.line != rhs.line) + return false; + if (lhs.col != rhs.col) + return false; + return true; +} +inline bool operator!=(const SourceLocation& lhs, const SourceLocation& rhs) +{ + return !(lhs == rhs); +} + template class TemplateImpl : public ITemplateImpl { @@ -327,6 +369,27 @@ class TemplateImpl : public ITemplateImpl nonstd::expected, ErrorInfoTpl> GetMetadataRaw() const { return m_metadataInfo; } + bool operator==(const TemplateImpl& other) const + { + if (m_env && other.m_env) + { + if (*m_env != *other.m_env) + return false; + } + if (m_settings != other.m_settings) + return false; + if (m_template != other.m_template) + return false; + if (m_renderer && other.m_renderer && !m_renderer->IsEqual(*other.m_renderer)) + return false; + if (m_metadata != other.m_metadata) + return false; + if (m_metadataJson != other.m_metadataJson) + return false; + if (m_metadataInfo != other.m_metadataInfo) + return false; + return true; + } private: void ThrowRuntimeError(ErrorCode code, ValuesList extraParams) { @@ -380,14 +443,36 @@ class TemplateImpl : public ITemplateImpl m_host->ThrowRuntimeError(code, std::move(extraParams)); } + bool IsEqual(const IComparable& other) const override + { + auto* callback = dynamic_cast(&other); + if (!callback) + return false; + if (m_host && callback->m_host) + return *m_host == *(callback->m_host); + if ((!m_host && (callback->m_host)) || (m_host && !(callback->m_host))) + return false; + return true; + } + bool operator==(const IComparable& other) const + { + auto* callback = dynamic_cast(&other); + if (!callback) + return false; + if (m_host && callback->m_host) + return *m_host == *(callback->m_host); + if ((!m_host && (callback->m_host)) || (m_host && !(callback->m_host))) + return false; + return true; + } + private: ThisType* m_host; }; - private: using JsonDocumentType = rapidjson::GenericDocument::type>; - TemplateEnv* m_env; + TemplateEnv* m_env{}; Settings m_settings; std::basic_string m_template; std::string m_templateName; diff --git a/src/testers.cpp b/src/testers.cpp index 52fef10f..bf18bb99 100644 --- a/src/testers.cpp +++ b/src/testers.cpp @@ -192,7 +192,7 @@ struct ValueKindGetter : visitors::BaseVisitor { return ValueKind::Callable; } - ValueKind operator()(RendererBase*) const + ValueKind operator()(IRendererBase*) const { return ValueKind::Renderer; } @@ -254,7 +254,7 @@ bool ValueTester::Test(const InternalValue& baseVal, RenderContext& context) { bool isConverted = false; auto seq = GetArgumentValue("seq", context); - auto seqKind = Apply(seq); + auto seqKind = Apply(seq); if (seqKind == ValueKind::List) { ListAdapter values = ConvertToList(seq, InternalValue(), isConverted); @@ -372,7 +372,7 @@ bool UserDefinedTester::Test(const InternalValue& baseVal, RenderContext& contex InternalValue result; if (callable->GetType() != Callable::Type::Expression) return false; - + return ConvertToBool(callable->GetExpressionCallable()(callParams, context)); } } // namespace testers diff --git a/src/testers.h b/src/testers.h index 9bf69918..9d29e6cb 100644 --- a/src/testers.h +++ b/src/testers.h @@ -29,6 +29,13 @@ class Comparator : public TesterBase Comparator(TesterParams params, BinaryExpression::Operation op); bool Test(const InternalValue& baseVal, RenderContext& context) override; + bool IsEqual(const IComparable& other) const override + { + auto* val = dynamic_cast(&other); + if (!val) + return false; + return m_op == val->m_op; + } private: BinaryExpression::Operation m_op; }; @@ -40,6 +47,13 @@ class StartsWith : public IsExpression::ITester bool Test(const InternalValue& baseVal, RenderContext& context) override; + bool IsEqual(const IComparable& other) const override + { + auto* val = dynamic_cast(&other); + if (!val) + return false; + return m_stringEval == val->m_stringEval; + } private: ExpressionEvaluatorPtr<> m_stringEval; }; @@ -66,6 +80,14 @@ class ValueTester : public TesterBase ValueTester(TesterParams params, Mode mode); bool Test(const InternalValue& baseVal, RenderContext& context) override; + + bool IsEqual(const IComparable& other) const override + { + auto* val = dynamic_cast(&other); + if (!val) + return false; + return m_mode == val->m_mode; + } private: Mode m_mode; }; @@ -77,6 +99,13 @@ class UserDefinedTester : public TesterBase bool Test(const InternalValue& baseVal, RenderContext& context) override; + bool IsEqual(const IComparable& other) const override + { + auto* val = dynamic_cast(&other); + if (!val) + return false; + return m_testerName == val->m_testerName && m_callParams == val->m_callParams; + } private: std::string m_testerName; TesterParams m_callParams; diff --git a/src/value_visitors.h b/src/value_visitors.h index 91659846..6157d292 100644 --- a/src/value_visitors.h +++ b/src/value_visitors.h @@ -177,7 +177,7 @@ struct ValueRendererBase void operator()(const KeyValuePair&) const {} void operator()(const Callable&) const {} void operator()(const UserCallable&) const {} - void operator()(const std::shared_ptr) const {} + void operator()(const std::shared_ptr) const {} template void operator()(const boost::recursive_wrapper&) const {} template @@ -304,13 +304,13 @@ struct InputValueConvertor template result_t operator()(const RecWrapper& val) const { - return this->operator()(const_cast(*val.get())); + return this->operator()(const_cast(*val)); } template result_t operator()(RecWrapper& val) const { - return this->operator()(*val.get()); + return this->operator()(*val); } template @@ -321,9 +321,8 @@ struct InputValueConvertor static result_t ConvertUserCallable(const UserCallable& val); - bool m_byValue; - bool m_allowStringRef; - + bool m_byValue{}; + bool m_allowStringRef{}; }; template @@ -521,6 +520,7 @@ struct UnaryOperation : BaseVisitor struct BinaryMathOperation : BaseVisitor<> { using BaseVisitor::operator (); + using ResultType = InternalValue; // InternalValue operator() (int, int) const {return InternalValue();} bool AlmostEqual(double x, double y) const @@ -535,9 +535,9 @@ struct BinaryMathOperation : BaseVisitor<> { } - InternalValue operator() (double left, double right) const + ResultType operator() (double left, double right) const { - InternalValue result = 0.0; + ResultType result = 0.0; switch (m_oper) { case jinja2::BinaryExpression::Plus: @@ -589,9 +589,9 @@ struct BinaryMathOperation : BaseVisitor<> return result; } - InternalValue operator() (int64_t left, int64_t right) const + ResultType operator() (int64_t left, int64_t right) const { - InternalValue result; + ResultType result; switch (m_oper) { case jinja2::BinaryExpression::Plus: @@ -636,85 +636,85 @@ struct BinaryMathOperation : BaseVisitor<> return result; } - InternalValue operator() (int64_t left, double right) const + ResultType operator() (int64_t left, double right) const { return this->operator ()(static_cast(left), static_cast(right)); } - InternalValue operator() (double left, int64_t right) const + ResultType operator() (double left, int64_t right) const { return this->operator ()(static_cast(left), static_cast(right)); } template - InternalValue operator() (const std::basic_string &left, const std::basic_string &right) const + ResultType operator() (const std::basic_string &left, const std::basic_string &right) const { return ProcessStrings(nonstd::basic_string_view(left), nonstd::basic_string_view(right)); } template - std::enable_if_t::value, InternalValue> operator() (const std::basic_string& left, const std::basic_string& right) const + std::enable_if_t::value, ResultType> operator() (const std::basic_string& left, const std::basic_string& right) const { auto rightStr = ConvertString>(right); return ProcessStrings(nonstd::basic_string_view(left), nonstd::basic_string_view(rightStr)); } template - InternalValue operator() (const nonstd::basic_string_view &left, const std::basic_string &right) const + ResultType operator() (const nonstd::basic_string_view &left, const std::basic_string &right) const { return ProcessStrings(left, nonstd::basic_string_view(right)); } template - std::enable_if_t::value, InternalValue> operator() (const nonstd::basic_string_view& left, const std::basic_string& right) const + std::enable_if_t::value, ResultType> operator() (const nonstd::basic_string_view& left, const std::basic_string& right) const { auto rightStr = ConvertString>(right); return ProcessStrings(left, nonstd::basic_string_view(rightStr)); } template - InternalValue operator() (const std::basic_string &left, const nonstd::basic_string_view &right) const + ResultType operator() (const std::basic_string &left, const nonstd::basic_string_view &right) const { return ProcessStrings(nonstd::basic_string_view(left), right); } template - std::enable_if_t::value, InternalValue> operator() (const std::basic_string& left, const nonstd::basic_string_view& right) const + std::enable_if_t::value, ResultType> operator() (const std::basic_string& left, const nonstd::basic_string_view& right) const { auto rightStr = ConvertString>(right); return ProcessStrings(nonstd::basic_string_view(left), nonstd::basic_string_view(rightStr)); } template - InternalValue operator() (const nonstd::basic_string_view &left, const nonstd::basic_string_view &right) const + ResultType operator() (const nonstd::basic_string_view &left, const nonstd::basic_string_view &right) const { return ProcessStrings(left, right); } template - std::enable_if_t::value, InternalValue> operator() (const nonstd::basic_string_view& left, const nonstd::basic_string_view& right) const + std::enable_if_t::value, ResultType> operator() (const nonstd::basic_string_view& left, const nonstd::basic_string_view& right) const { auto rightStr = ConvertString>(right); return ProcessStrings(left, nonstd::basic_string_view(rightStr)); } template - InternalValue operator() (const std::basic_string &left, int64_t right) const + ResultType operator() (const std::basic_string &left, int64_t right) const { return RepeatString(nonstd::basic_string_view(left), right); } template - InternalValue operator() (const nonstd::basic_string_view &left, int64_t right) const + ResultType operator() (const nonstd::basic_string_view &left, int64_t right) const { return RepeatString(left, right); } template - InternalValue RepeatString(const nonstd::basic_string_view& left, const int64_t right) const + ResultType RepeatString(const nonstd::basic_string_view& left, const int64_t right) const { using string = std::basic_string; - InternalValue result; + ResultType result; if(m_oper == jinja2::BinaryExpression::Mul) { @@ -727,10 +727,10 @@ struct BinaryMathOperation : BaseVisitor<> } template - InternalValue ProcessStrings(const nonstd::basic_string_view& left, const nonstd::basic_string_view& right) const + ResultType ProcessStrings(const nonstd::basic_string_view& left, const nonstd::basic_string_view& right) const { using string = std::basic_string; - InternalValue result; + ResultType result; switch (m_oper) { @@ -780,9 +780,9 @@ struct BinaryMathOperation : BaseVisitor<> return result; } - InternalValue operator() (const KeyValuePair& left, const KeyValuePair& right) const + ResultType operator() (const KeyValuePair& left, const KeyValuePair& right) const { - InternalValue result; + ResultType result; switch (m_oper) { case jinja2::BinaryExpression::LogicalEq: @@ -798,9 +798,9 @@ struct BinaryMathOperation : BaseVisitor<> return result; } - InternalValue operator() (const ListAdapter& left, const ListAdapter& right) const + ResultType operator() (const ListAdapter& left, const ListAdapter& right) const { - InternalValue result; + ResultType result; if (m_oper == jinja2::BinaryExpression::Plus) { InternalValueList values; @@ -815,9 +815,9 @@ struct BinaryMathOperation : BaseVisitor<> return result; } - InternalValue operator() (const ListAdapter& left, int64_t right) const + ResultType operator() (const ListAdapter& left, int64_t right) const { - InternalValue result; + ResultType result; if (right >= 0 && m_oper == jinja2::BinaryExpression::Mul) { InternalValueList values; @@ -832,9 +832,9 @@ struct BinaryMathOperation : BaseVisitor<> return result; } - InternalValue operator() (bool left, bool right) const + ResultType operator() (bool left, bool right) const { - InternalValue result; + ResultType result; switch (m_oper) { case jinja2::BinaryExpression::LogicalEq: @@ -853,9 +853,9 @@ struct BinaryMathOperation : BaseVisitor<> return result; } - InternalValue operator() (EmptyValue, EmptyValue) const + ResultType operator() (EmptyValue, EmptyValue) const { - InternalValue result; + ResultType result; switch (m_oper) { case jinja2::BinaryExpression::LogicalEq: @@ -872,9 +872,9 @@ struct BinaryMathOperation : BaseVisitor<> } template - InternalValue operator() (EmptyValue, T&&) const + ResultType operator() (EmptyValue, T&&) const { - InternalValue result; + ResultType result; switch (m_oper) { case jinja2::BinaryExpression::LogicalEq: @@ -891,9 +891,9 @@ struct BinaryMathOperation : BaseVisitor<> } template - InternalValue operator() (T&&, EmptyValue) const + ResultType operator() (T&&, EmptyValue) const { - InternalValue result; + ResultType result; switch (m_oper) { case jinja2::BinaryExpression::LogicalEq: @@ -1134,6 +1134,18 @@ auto GetAsSameString(const nonstd::basic_string_view&, const InternalValu return Result(); } +inline bool operator==(const InternalValueData& lhs, const InternalValueData& rhs) +{ + InternalValue cmpRes; + cmpRes = Apply2(lhs, rhs, BinaryExpression::LogicalEq); + return ConvertToBool(cmpRes); +} + +inline bool operator!=(const InternalValueData& lhs, const InternalValueData& rhs) +{ + return !(lhs == rhs); +} + } // namespace jinja2 #endif // JINJA2CPP_SRC_VALUE_VISITORS_H From 4c588f632cfc76a56611f2e2a8f3966d7f163020 Mon Sep 17 00:00:00 2001 From: rmorozov Date: Sun, 26 Dec 2021 22:19:59 +0300 Subject: [PATCH 02/14] Create codeql-analysis.yml --- .github/workflows/codeql-analysis.yml | 70 +++++++++++++++++++++++++++ 1 file changed, 70 insertions(+) create mode 100644 .github/workflows/codeql-analysis.yml diff --git a/.github/workflows/codeql-analysis.yml b/.github/workflows/codeql-analysis.yml new file mode 100644 index 00000000..6e119438 --- /dev/null +++ b/.github/workflows/codeql-analysis.yml @@ -0,0 +1,70 @@ +# For most projects, this workflow file will not need changing; you simply need +# to commit it to your repository. +# +# You may wish to alter this file to override the set of languages analyzed, +# or to provide custom queries or build logic. +# +# ******** NOTE ******** +# We have attempted to detect the languages in your repository. Please check +# the `language` matrix defined below to confirm you have the correct set of +# supported CodeQL languages. +# +name: "CodeQL" + +on: + push: + branches: [ master ] + pull_request: + # The branches below must be a subset of the branches above + branches: [ master ] + schedule: + - cron: '23 15 * * 2' + +jobs: + analyze: + name: Analyze + runs-on: ubuntu-latest + permissions: + actions: read + contents: read + security-events: write + + strategy: + fail-fast: false + matrix: + language: [ 'cpp' ] + # CodeQL supports [ 'cpp', 'csharp', 'go', 'java', 'javascript', 'python', 'ruby' ] + # Learn more about CodeQL language support at https://git.io/codeql-language-support + + steps: + - name: Checkout repository + uses: actions/checkout@v2 + + # Initializes the CodeQL tools for scanning. + - name: Initialize CodeQL + uses: github/codeql-action/init@v1 + with: + languages: ${{ matrix.language }} + # If you wish to specify custom queries, you can do so here or in a config file. + # By default, queries listed here will override any specified in a config file. + # Prefix the list here with "+" to use these queries and those in the config file. + # queries: ./path/to/local/query, your-org/your-repo/queries@main + + # Autobuild attempts to build any compiled languages (C/C++, C#, or Java). + # If this step fails, then you should remove it and run the build manually (see below) + - name: Autobuild + uses: github/codeql-action/autobuild@v1 + + # ℹ️ Command-line programs to run using the OS shell. + # 📚 https://git.io/JvXDl + + # ✏️ If the Autobuild fails above, remove it and uncomment the following three lines + # and modify them (or add more) to build your code if your project + # uses a compiled language + + #- run: | + # make bootstrap + # make release + + - name: Perform CodeQL Analysis + uses: github/codeql-action/analyze@v1 From 1dacf16a1f6e216f5dcb7e7b8ef9626634c29cf9 Mon Sep 17 00:00:00 2001 From: rmorozov Date: Sat, 8 Jan 2022 16:17:58 +0300 Subject: [PATCH 03/14] Update README.md Fix links to site --- README.md | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index 8894975d..61b0f32c 100644 --- a/README.md +++ b/README.md @@ -7,11 +7,10 @@ [![Standard](https://img.shields.io/badge/c%2B%2B-17-blue.svg)](https://en.wikipedia.org/wiki/C%2B%2B#Standardization) [![Standard](https://img.shields.io/badge/c%2B%2B-20-blue.svg)](https://en.wikipedia.org/wiki/C%2B%2B#Standardization) [![Coverage Status](https://codecov.io/gh/jinja2cpp/Jinja2Cpp/branch/master/graph/badge.svg)](https://codecov.io/gh/jinja2cpp/Jinja2Cpp) -[![Codacy Badge](https://api.codacy.com/project/badge/Grade/ff01fa4410ac417f8192dce78e919ece)](https://www.codacy.com/app/flexferrum/Jinja2Cpp_2?utm_source=github.com&utm_medium=referral&utm_content=jinja2cpp/Jinja2Cpp&utm_campaign=Badge_Grade) [![Github Releases](https://img.shields.io/github/release/jinja2cpp/Jinja2Cpp/all.svg)](https://github.com/jinja2cpp/Jinja2Cpp/releases) [![Github Issues](https://img.shields.io/github/issues/jinja2cpp/Jinja2Cpp.svg)](http://github.com/jinja2cpp/Jinja2Cpp/issues) [![GitHub License](https://img.shields.io/badge/license-Mozilla-blue.svg)](https://raw.githubusercontent.com/jinja2cpp/Jinja2Cpp/master/LICENSE) -[![conan.io](https://api.bintray.com/packages/conan/conan-center/jinja2cpp%3A_/images/download.svg?version=1.1.0%3A_) ](https://bintray.com/conan/conan-center/jinja2cpp%3A_/1.1.0%3A_/link) +[![conan.io](https://api.bintray.com/packages/conan/conan-center/jinja2cpp%3A_/images/download.svg?version=1.2.1%3A_) ](https://conan.io/center/jinja2cpp) [![Gitter Chat](https://badges.gitter.im/Join%20Chat.svg)](https://gitter.im/Jinja2Cpp/Lobby) C++ implementation of the Jinja2 Python template engine. This library brings support of powerful Jinja2 template features into the C++ world, reports dynamic HTML pages and source code generation. @@ -61,7 +60,7 @@ hello; world!!! To use Jinja2C++ in your project you have to: * Clone the Jinja2C++ repository -* Build it according to the [instructions](https://jinja2cpp.dev/docs/build_and_install.html) +* Build it according to the [instructions](https://jinja2cpp.github.io/docs/build_and_install.html) * Link to your project. Usage of Jinja2C++ in the code is pretty simple: @@ -91,7 +90,7 @@ Hello World!!! That's all! -More detailed examples and features description can be found in the documentation: [https://jinja2cpp.dev/docs/usage](https://jinja2cpp.dev/docs/usage) +More detailed examples and features description can be found in the documentation: [https://jinja2cpp.dev/docs/usage](https://jinja2cpp.github.io/docs/usage) ## Current Jinja2 support Currently, Jinja2C++ supports the limited number of Jinja2 features. By the way, Jinja2C++ is planned to be a fully [jinja2 specification](http://jinja.pocoo.org/docs/2.10/templates/)-conformant. The current support is limited to: @@ -112,7 +111,7 @@ Currently, Jinja2C++ supports the limited number of Jinja2 features. By the way, - recursive loops - space control and 'raw'/'endraw' blocks -Full information about Jinja2 specification support and compatibility table can be found here: [https://jinja2cpp.dev/docs/j2_compatibility.html](https://jinja2cpp.dev/docs/j2_compatibility.html). +Full information about Jinja2 specification support and compatibility table can be found here: [https://jinja2cpp.dev/docs/j2_compatibility.html](https://jinja2cpp.github.io/docs/j2_compatibility.html). ## Supported compilers Compilation of Jinja2C++ tested on the following compilers (with C++14 and C++17 enabled features): From e1d5577c85a8bdfe296e50f64329c7382feecbd5 Mon Sep 17 00:00:00 2001 From: rmorozov Date: Sun, 11 Dec 2022 22:15:25 +0300 Subject: [PATCH 04/14] bump dups & add boost json support (#226) addresses #223, #230 * bump deps * update boost version to 1.79.0 * update googletest to latest version * update nlohmann.json to v3.10.5 * update rapidjson to latest * update fmt to latest * add support for boost::json * fix build * fix code factor issue * add pipelines verbosity * move boost to master * update boost to 1.80.0 * fix actions for clang * replace boost submobule with fetchcontent * fix test on windows * patch boost cmake to workaround install export issue * fix path to header * disable strict warnings for linux build * try to use vs 2017 on windows 2019 * remove msvc2017 from ci build * remove comments * change to interface option --- .clang-format | 1 + .github/workflows/codeql-analysis.yml | 22 +- .github/workflows/linux-build.yml | 72 +----- .github/workflows/windows-build.yml | 15 +- .gitmodules | 6 +- CMakeLists.txt | 16 +- .../patches/0001-fix-skip-install-rules.patch | 25 ++ include/jinja2cpp/binding/boost_json.h | 218 ++++++++++++++++++ include/jinja2cpp/binding/nlohmann_json.h | 1 + include/jinja2cpp/binding/rapid_json.h | 1 + src/{ => binding}/rapid_json_serializer.cpp | 2 +- src/{ => binding}/rapid_json_serializer.h | 2 +- src/filters.cpp | 2 +- src/out_stream.h | 1 + src/serialize_filters.cpp | 2 +- test/binding/boost_json_binding_test.cpp | 177 ++++++++++++++ .../nlohmann_json_binding_test.cpp | 5 +- .../{ => binding}/rapid_json_binding_test.cpp | 5 +- .../rapid_json_serializer_test.cpp | 8 +- thirdparty/CMakeLists.txt | 2 +- thirdparty/boost | 2 +- thirdparty/external_boost_deps.cmake | 30 +-- thirdparty/fmtlib | 2 +- thirdparty/json/nlohmann | 2 +- thirdparty/json/rapid | 2 +- thirdparty/thirdparty-conan-build.cmake | 10 +- thirdparty/thirdparty-external-boost.cmake | 2 +- thirdparty/thirdparty-internal.cmake | 16 +- 28 files changed, 520 insertions(+), 129 deletions(-) create mode 100644 cmake/patches/0001-fix-skip-install-rules.patch create mode 100644 include/jinja2cpp/binding/boost_json.h rename src/{ => binding}/rapid_json_serializer.cpp (99%) rename src/{ => binding}/rapid_json_serializer.h (97%) create mode 100644 test/binding/boost_json_binding_test.cpp rename test/{ => binding}/nlohmann_json_binding_test.cpp (98%) rename test/{ => binding}/rapid_json_binding_test.cpp (98%) rename test/{ => binding}/rapid_json_serializer_test.cpp (96%) diff --git a/.clang-format b/.clang-format index f2a3a7bb..36b586fb 100644 --- a/.clang-format +++ b/.clang-format @@ -13,6 +13,7 @@ BraceWrapping: AfterStruct: true AccessModifierOffset: -4 BinPackParameters: false +AlignAfterOpenBracket: AlwaysBreak AlwaysBreakAfterReturnType: None AlwaysBreakAfterDefinitionReturnType: None AllowAllParametersOfDeclarationOnNextLine: false diff --git a/.github/workflows/codeql-analysis.yml b/.github/workflows/codeql-analysis.yml index 6e119438..bfed1c63 100644 --- a/.github/workflows/codeql-analysis.yml +++ b/.github/workflows/codeql-analysis.yml @@ -52,8 +52,8 @@ jobs: # Autobuild attempts to build any compiled languages (C/C++, C#, or Java). # If this step fails, then you should remove it and run the build manually (see below) - - name: Autobuild - uses: github/codeql-action/autobuild@v1 + # - name: Autobuild + # uses: github/codeql-action/autobuild@v1 # ℹ️ Command-line programs to run using the OS shell. # 📚 https://git.io/JvXDl @@ -66,5 +66,21 @@ jobs: # make bootstrap # make release + - name: Build + run: | + #!/bin/bash + set -ex + export BUILD_TARGET=all + export CMAKE_OPTS=-DCMAKE_VERBOSE_MAKEFILE=ON + export WORKSPACE=$GITHUB_WORKSPACE + if [ "${INPUT_COMPILER}" == "clang-12" ] ; then + export INPUT_BASE_FLAGS="-DJINJA2CPP_CXX_STANDARD=20" ; + fi + export EXTRA_FLAGS="${INPUT_BASE_FLAGS} ${INPUT_EXTRA_FLAGS}" + mkdir $BUILD_DIRECTORY && cd $BUILD_DIRECTORY + sudo chmod gou+rw -R $WORKSPACE + cmake $CMAKE_OPTS -DCMAKE_BUILD_TYPE=Release -DCMAKE_CXX_FLAGS=$CMAKE_CXX_FLAGS -DJINJA2CPP_DEPS_MODE=internal -DJINJA2CPP_BUILD_SHARED=ON $EXTRA_FLAGS $WORKSPACE && cmake --build . --config Release --target all -- -j4 + + - name: Perform CodeQL Analysis - uses: github/codeql-action/analyze@v1 + uses: github/codeql-action/analyze@v2 diff --git a/.github/workflows/linux-build.yml b/.github/workflows/linux-build.yml index 44bdbf53..7e283b24 100644 --- a/.github/workflows/linux-build.yml +++ b/.github/workflows/linux-build.yml @@ -18,7 +18,7 @@ on: - '**.md' jobs: - linux-gcc-build: + linux-build: runs-on: ubuntu-latest @@ -26,7 +26,7 @@ jobs: fail-fast: false max-parallel: 8 matrix: - compiler: [g++-9, g++-10, g++-11] + compiler: [g++-9, g++-10, g++-11, clang++-12, clang++-13, clang++-14] base-flags: ["", -DJINJA2CPP_CXX_STANDARD=17] build-config: [Release, Debug] build-shared: [TRUE, FALSE] @@ -40,7 +40,7 @@ jobs: extra-flags: -DJINJA2CPP_STRICT_WARNINGS=OFF steps: - - uses: actions/checkout@v1 + - uses: actions/checkout@v2 - name: Setup environment env: INPUT_COMPILER: ${{ matrix.compiler }} @@ -79,9 +79,10 @@ jobs: if [[ "${INPUT_COMPILER}" != "" ]]; then export CXX=${INPUT_COMPILER}; fi export BUILD_CONFIG=${INPUT_BASE_CONFIG} $CXX --version + cmake --version export EXTRA_FLAGS="${INPUT_BASE_FLAGS} ${INPUT_EXTRA_FLAGS}" mkdir -p .build && cd .build - cmake $CMAKE_OPTS -DCMAKE_BUILD_TYPE=$BUILD_CONFIG -DCMAKE_CXX_FLAGS=$CMAKE_CXX_FLAGS -DJINJA2CPP_DEPS_MODE=internal -DJINJA2CPP_BUILD_SHARED=$INPUT_BUILD_SHARED $EXTRA_FLAGS .. && cmake --build . --config $BUILD_CONFIG --target all -- -j4 + cmake $CMAKE_OPTS -DCMAKE_BUILD_TYPE=$BUILD_CONFIG -DCMAKE_CXX_FLAGS=$CMAKE_CXX_FLAGS -DJINJA2CPP_DEPS_MODE=internal -DJINJA2CPP_STRICT_WARNINGS=OFF -DJINJA2CPP_BUILD_SHARED=$INPUT_BUILD_SHARED $EXTRA_FLAGS .. && cmake --build . --config $BUILD_CONFIG --target all -- -j4 - name: Test env: @@ -89,66 +90,3 @@ jobs: run: | cd .build && ctest -C $BUILD_CONFIG -V - linux-clang-build: - - runs-on: ubuntu-latest - container: - image: ${{matrix.docker-image}} - env: - BUILD_DIRECTORY: /home/conan/.build - HOME: /home/conan - - strategy: - fail-fast: false - max-parallel: 8 - matrix: - compiler: [10, 11, 12] - base-flags: ["", -DJINJA2CPP_CXX_STANDARD=17] - build-config: [Release, Debug] - build-shared: [TRUE, FALSE] - - include: - - compiler: 10 - docker-image: conanio/clang10 - - compiler: 11 - docker-image: conanio/clang11 - - compiler: 12 - docker-image: conanio/clang12-ubuntu16.04:1.39.0 - - - steps: - - uses: actions/checkout@v1 - - - name: Build - env: - INPUT_COMPILER: clang-${{ matrix.compiler }} - INPUT_BASE_FLAGS: ${{ matrix.base-flags }} - INPUT_BASE_CONFIG: ${{ matrix.build-config }} - INPUT_EXTRA_FLAGS: ${{ matrix.extra-flags }} - INPUT_BUILD_SHARED: ${{ matrix.build-shared }} - HOME: /home/conan - run: | - #!/bin/bash - set -ex - export BUILD_TARGET=all - export CMAKE_OPTS=-DCMAKE_VERBOSE_MAKEFILE=OFF - export BUILD_CONFIG=${INPUT_BASE_CONFIG} - export WORKSPACE=$GITHUB_WORKSPACE - #if [ "${INPUT_COMPILER}" != "" ]; then export CXX=${INPUT_COMPILER}; fi - if [ "${INPUT_COMPILER}" == "clang-12" ] ; then - export INPUT_BASE_FLAGS="-DJINJA2CPP_CXX_STANDARD=20" ; - fi - #$CXX --version - export EXTRA_FLAGS="${INPUT_BASE_FLAGS} ${INPUT_EXTRA_FLAGS}" - mkdir $BUILD_DIRECTORY && cd $BUILD_DIRECTORY - sudo chmod gou+rw -R $WORKSPACE - cmake $CMAKE_OPTS -DCMAKE_BUILD_TYPE=$BUILD_CONFIG -DCMAKE_CXX_FLAGS=$CMAKE_CXX_FLAGS -DJINJA2CPP_DEPS_MODE=internal -DJINJA2CPP_BUILD_SHARED=$INPUT_BUILD_SHARED $EXTRA_FLAGS $WORKSPACE && cmake --build . --config $BUILD_CONFIG --target all -- -j4 - shell: bash - - - name: Test - env: - BUILD_CONFIG: ${{ matrix.build-config }} - run: | - cd $BUILD_DIRECTORY - ctest -C $BUILD_CONFIG -V - diff --git a/.github/workflows/windows-build.yml b/.github/workflows/windows-build.yml index dedd5179..1204e857 100644 --- a/.github/workflows/windows-build.yml +++ b/.github/workflows/windows-build.yml @@ -25,7 +25,7 @@ jobs: fail-fast: false max-parallel: 20 matrix: - compiler: [msvc-2019, msvc-2017] + compiler: [msvc-2019] base-flags: ["", -DJINJA2CPP_CXX_STANDARD=17] build-config: [Release, Debug] build-platform: [x86, x64] @@ -33,16 +33,6 @@ jobs: build-shared: [FALSE, TRUE] include: - - compiler: msvc-2017 - build-platform: x86 - run-machine: windows-2016 - generator: Visual Studio 15 2017 - vc_vars: C:\Program Files (x86)\Microsoft Visual Studio\2017\Enterprise\VC\Auxiliary\Build\vcvars32.bat - - compiler: msvc-2017 - build-platform: x64 - run-machine: windows-2016 - generator: Visual Studio 15 2017 Win64 - vc_vars: C:\Program Files (x86)\Microsoft Visual Studio\2017\Enterprise\VC\Auxiliary\Build\vcvars64.bat - compiler: msvc-2019 build-platform: x86 run-machine: windows-2019 @@ -56,7 +46,7 @@ jobs: steps: - - uses: actions/checkout@v1 + - uses: actions/checkout@v2 - name: Build shell: cmd @@ -74,6 +64,7 @@ jobs: call "%VC_VARS%" mkdir -p .build cd .build + cmake --version cmake .. -G "%INPUT_GENERATOR%" -DCMAKE_BUILD_TYPE=%INPUT_BUILD_CONFIG% -DJINJA2CPP_MSVC_RUNTIME_TYPE="%INPUT_BUILD_RUNTIME%" -DJINJA2CPP_DEPS_MODE=internal -DJINJA2CPP_BUILD_SHARED=%INPUT_BUILD_SHARED% %INPUT_BASE_FLAGS% %INPUT_EXTRA_FLAGS% cmake --build . --config %INPUT_BUILD_CONFIG% --verbose diff --git a/.gitmodules b/.gitmodules index 69f3f156..0b3e6045 100644 --- a/.gitmodules +++ b/.gitmodules @@ -1,9 +1,9 @@ [submodule "thirdparty/gtest"] path = thirdparty/gtest url = https://github.com/google/googletest.git -[submodule "thirdparty/boost"] - path = thirdparty/boost - url = https://github.com/boostorg/boost.git +#[submodule "thirdparty/boost"] +# path = thirdparty/boost +# url = https://github.com/boostorg/boost.git [submodule "thirdparty/nonstd/expected-lite"] path = thirdparty/nonstd/expected-lite url = https://github.com/martinmoene/expected-lite.git diff --git a/CMakeLists.txt b/CMakeLists.txt index 4bd7fd8a..51872780 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -15,6 +15,9 @@ endif() set(JINJA2CPP_SANITIZERS address+undefined memory) set(JINJA2CPP_WITH_SANITIZERS none CACHE STRING "Build with sanitizer") set_property(CACHE JINJA2CPP_WITH_SANITIZERS PROPERTY STRINGS ${JINJA2CPP_SANITIZERS}) +set(JINJA2CPP_WITH_JSON_BINDINGS boost nlohmann rapid all none) +set(JINJA2CPP_WITH_JSON_BINDINGS all CACHE STRING "Build with json support") +set_property(CACHE JINJA2CPP_WITH_JSON_BINDINGS PROPERTY STRINGS ${JINJA2CPP_WITH_JSON_BINDINGS}) set (JINJA2CPP_DEPS_MODE "internal" CACHE STRING "Jinja2Cpp dependency management mode (internal | external | external-boost | conan-build). See documentation for details. 'interal' is default.") option(JINJA2CPP_BUILD_TESTS "Build Jinja2Cpp unit tests" ${JINJA2CPP_IS_MAIN_PROJECT}) option(JINJA2CPP_STRICT_WARNINGS "Enable additional warnings and treat them as errors" ON) @@ -263,13 +266,16 @@ macro (Jinja2CppGetTargetIncludeDir infix target) if (TARGET ${target}) set (_J2CPP_VAR_NAME JINJA2CPP_${infix}_INCLUDE_DIRECTORIES) get_target_property(${_J2CPP_VAR_NAME} ${target} INTERFACE_INCLUDE_DIRECTORIES) - endif () + else () + message(WARNING "No target: ${target}") + endif() + endmacro () -Jinja2CppGetTargetIncludeDir(EXPECTED-LITE expected-lite) -Jinja2CppGetTargetIncludeDir(VARIANT-LITE variant-lite) -Jinja2CppGetTargetIncludeDir(OPTIONAL-LITE optional-lite) -Jinja2CppGetTargetIncludeDir(STRING-VIEW-LITE string-view-lite) +Jinja2CppGetTargetIncludeDir(EXPECTED-LITE nonstd::expected-lite) +Jinja2CppGetTargetIncludeDir(VARIANT-LITE nonstd::variant-lite) +Jinja2CppGetTargetIncludeDir(OPTIONAL-LITE nonstd::optional-lite) +Jinja2CppGetTargetIncludeDir(STRING-VIEW-LITE nonstd::string-view-lite) # Workaround for #14444 bug of CMake (https://gitlab.kitware.com/cmake/cmake/issues/14444) # We can't use EXPORT feature of 'install' as is due to limitation of subproject's targets installation diff --git a/cmake/patches/0001-fix-skip-install-rules.patch b/cmake/patches/0001-fix-skip-install-rules.patch new file mode 100644 index 00000000..caa4299c --- /dev/null +++ b/cmake/patches/0001-fix-skip-install-rules.patch @@ -0,0 +1,25 @@ +From 857dc932fc32d3a1d30abac7724565812b446cd2 Mon Sep 17 00:00:00 2001 +From: Ruslan Morozov +Date: Thu, 8 Dec 2022 21:11:49 +0300 +Subject: [PATCH] fix skip install rules + +--- + include/BoostRoot.cmake | 2 +- + 1 file changed, 1 insertion(+), 1 deletion(-) + +diff --git a/tools/cmake/include/BoostRoot.cmake b/tools/cmake/include/BoostRoot.cmake +index f2a51e1..a37f14d 100644 +--- a/tools/cmake/include/BoostRoot.cmake ++++ b/tools/cmake/include/BoostRoot.cmake +@@ -90,7 +90,7 @@ else() + endif() + + set(BUILD_TESTING OFF) +- set(CMAKE_SKIP_INSTALL_RULES ON) ++ set(CMAKE_SKIP_INSTALL_RULES OFF) + + endif() + +-- +2.37.1 (Apple Git-137.1) + diff --git a/include/jinja2cpp/binding/boost_json.h b/include/jinja2cpp/binding/boost_json.h new file mode 100644 index 00000000..93564694 --- /dev/null +++ b/include/jinja2cpp/binding/boost_json.h @@ -0,0 +1,218 @@ +#ifndef JINJA2CPP_BINDING_BOOST_JSON_H +#define JINJA2CPP_BINDING_BOOST_JSON_H + +#include +#include +#include + +namespace jinja2 +{ +namespace detail +{ + +class BoostJsonObjectAccessor + : public IMapItemAccessor + , public ReflectedDataHolder +{ + struct SizeVisitor + { + size_t operator()(std::nullptr_t) { return {}; } + size_t operator()(bool) { return 1; } + size_t operator()(std::int64_t) { return 1; } + size_t operator()(std::uint64_t) { return 1; } + size_t operator()(double) { return 1; } + size_t operator()(const boost::json::string&) { return 1; } + size_t operator()(const boost::json::array& val) { return val.size(); } + size_t operator()(const boost::json::object& val) { return val.size(); } + size_t operator()(...) { return 0; } + }; + +public: + using ReflectedDataHolder::ReflectedDataHolder; + ~BoostJsonObjectAccessor() override = default; + + size_t GetSize() const override + { + auto j = this->GetValue(); + if (!j) + return {}; + // simulate nlohmann semantics + SizeVisitor sv; + return boost::json::visit(sv, *j); + } + + bool HasValue(const std::string& name) const override + { + auto j = this->GetValue(); + if (!j) + return false; + auto obj = j->if_object(); + return obj ? obj->contains(name) : false; + } + + Value GetValueByName(const std::string& name) const override + { + auto j = this->GetValue(); + if (!j) + return Value(); + auto obj = j->if_object(); + if (!obj) + return Value(); + auto val = obj->if_contains(name); + if (!val) + return Value(); + return Reflect(*val); + } + + std::vector GetKeys() const override + { + auto j = this->GetValue(); + if (!j) + return {}; + auto obj = j->if_object(); + if (!obj) + return {}; + std::vector result; + result.reserve(obj->size()); + for (auto& item : *obj) + { + result.emplace_back(item.key()); + } + return result; + } + bool IsEqual(const IComparable& other) const override + { + auto* val = dynamic_cast(&other); + if (!val) + return false; + return this->GetValue() == val->GetValue(); + } +}; + +struct BoostJsonArrayAccessor + : IListItemAccessor + , IIndexBasedAccessor + , ReflectedDataHolder +{ + using ReflectedDataHolder::ReflectedDataHolder; + + nonstd::optional GetSize() const override + { + auto j = this->GetValue(); + return j ? j->size() : nonstd::optional(); + } + + const IIndexBasedAccessor* GetIndexer() const override { return this; } + + ListEnumeratorPtr CreateEnumerator() const override + { + using Enum = Enumerator; + auto j = this->GetValue(); + if (!j) + return jinja2::ListEnumeratorPtr(); + + return jinja2::ListEnumeratorPtr(new Enum(j->begin(), j->end())); + } + + Value GetItemByIndex(int64_t idx) const override + { + auto j = this->GetValue(); + if (!j) + return Value(); + + return Reflect((*j)[idx]); + } + + bool IsEqual(const IComparable& other) const override + { + auto* val = dynamic_cast(&other); + if (!val) + return false; + return GetValue() == val->GetValue(); + } +}; + +template<> +struct Reflector +{ + static Value Create(boost::json::value val) + { + Value result; + switch (val.kind()) + { + default: // unreachable()? + case boost::json::kind::null: + break; + case boost::json::kind::bool_: + result = val.get_bool(); + break; + case boost::json::kind::int64: + result = val.get_int64(); + break; + case boost::json::kind::uint64: + result = static_cast(val.get_uint64()); + break; + case boost::json::kind::double_: + result = val.get_double(); + break; + case boost::json::kind::string: + result = std::string(val.get_string().c_str()); + break; + case boost::json::kind::array: { + auto array = val.get_array(); + result = GenericList([accessor = BoostJsonArrayAccessor(std::move(array))]() { return &accessor; }); + break; + } + case boost::json::kind::object: { + auto obj = val.get_object(); + result = GenericMap([accessor = BoostJsonObjectAccessor(std::move(val))]() { return &accessor; }); + break; + } + } + return result; + } + + static Value CreateFromPtr(const boost::json::value* val) + { + Value result; + switch (val->kind()) + { + default: // unreachable()? + case boost::json::kind::null: + break; + case boost::json::kind::bool_: + result = val->get_bool(); + break; + case boost::json::kind::int64: + result = val->get_int64(); + break; + case boost::json::kind::uint64: + result = static_cast(val->get_uint64()); + break; + case boost::json::kind::double_: + result = val->get_double(); + break; + case boost::json::kind::string: + result = std::string(val->get_string().c_str()); + break; + case boost::json::kind::array: + { + auto array = val->get_array(); + result = GenericList([accessor = BoostJsonArrayAccessor(std::move(array))]() { return &accessor; }); + break; + } + case boost::json::kind::object: + { + auto obj = val->get_object(); + result = GenericMap([accessor = BoostJsonObjectAccessor(std::move(val))]() { return &accessor; }); + break; + } + } + return result; + } +}; + +} // namespace detail +} // namespace jinja2 + +#endif // JINJA2CPP_BINDING_BOOST_JSON_H diff --git a/include/jinja2cpp/binding/nlohmann_json.h b/include/jinja2cpp/binding/nlohmann_json.h index b6a302a4..88403780 100644 --- a/include/jinja2cpp/binding/nlohmann_json.h +++ b/include/jinja2cpp/binding/nlohmann_json.h @@ -44,6 +44,7 @@ class NLohmannJsonObjectAccessor : public IMapItemAccessor, public ReflectedData return {}; std::vector result; + result.reserve(j->size()); for (auto& item : j->items()) { result.emplace_back(item.key()); diff --git a/include/jinja2cpp/binding/rapid_json.h b/include/jinja2cpp/binding/rapid_json.h index 88531314..f29a1413 100644 --- a/include/jinja2cpp/binding/rapid_json.h +++ b/include/jinja2cpp/binding/rapid_json.h @@ -65,6 +65,7 @@ class RapidJsonObjectAccessor : public IMapItemAccessor, public ReflectedDataHol return {}; std::vector result; + result.reserve(j->MemberCount()); for (auto it = j->MemberBegin(); it != j->MemberEnd(); ++ it) { result.emplace_back(ConvertString(nonstd::basic_string_view(it->name.GetString()))); diff --git a/src/rapid_json_serializer.cpp b/src/binding/rapid_json_serializer.cpp similarity index 99% rename from src/rapid_json_serializer.cpp rename to src/binding/rapid_json_serializer.cpp index 324269a8..bd7e5a32 100644 --- a/src/rapid_json_serializer.cpp +++ b/src/binding/rapid_json_serializer.cpp @@ -1,6 +1,6 @@ #include "rapid_json_serializer.h" -#include "value_visitors.h" +#include "../value_visitors.h" #include diff --git a/src/rapid_json_serializer.h b/src/binding/rapid_json_serializer.h similarity index 97% rename from src/rapid_json_serializer.h rename to src/binding/rapid_json_serializer.h index 5028cd66..66e26c14 100644 --- a/src/rapid_json_serializer.h +++ b/src/binding/rapid_json_serializer.h @@ -1,7 +1,7 @@ #ifndef JINJA2CPP_SRC_RAPID_JSON_SERIALIZER_H #define JINJA2CPP_SRC_RAPID_JSON_SERIALIZER_H -#include "internal_value.h" +#include "../internal_value.h" #include #include diff --git a/src/filters.cpp b/src/filters.cpp index f26deddd..fca0bd60 100644 --- a/src/filters.cpp +++ b/src/filters.cpp @@ -1,8 +1,8 @@ #include "filters.h" +#include "binding/rapid_json_serializer.h" #include "generic_adapters.h" #include "out_stream.h" -#include "rapid_json_serializer.h" #include "testers.h" #include "value_helpers.h" #include "value_visitors.h" diff --git a/src/out_stream.h b/src/out_stream.h index 7c750856..8e3f001b 100644 --- a/src/out_stream.h +++ b/src/out_stream.h @@ -2,6 +2,7 @@ #define JINJA2CPP_SRC_OUT_STREAM_H #include "internal_value.h" + #include #include #include diff --git a/src/serialize_filters.cpp b/src/serialize_filters.cpp index 3bc6950a..95e3fcd1 100644 --- a/src/serialize_filters.cpp +++ b/src/serialize_filters.cpp @@ -1,7 +1,7 @@ +#include "binding/rapid_json_serializer.h" #include "filters.h" #include "generic_adapters.h" #include "out_stream.h" -#include "rapid_json_serializer.h" #include "testers.h" #include "value_helpers.h" #include "value_visitors.h" diff --git a/test/binding/boost_json_binding_test.cpp b/test/binding/boost_json_binding_test.cpp new file mode 100644 index 00000000..f8ec4531 --- /dev/null +++ b/test/binding/boost_json_binding_test.cpp @@ -0,0 +1,177 @@ +#include +#include + +#include +#include + +#include "../test_tools.h" +#include + +using BoostJsonTest = BasicTemplateRenderer; + +MULTISTR_TEST(BoostJsonTest, BasicReflection, R"({{ json.message }})", R"(Hello World from Parser!)") +{ + boost::json::value values = { + {"message", "Hello World from Parser!"} + }; + + params["json"] = jinja2::Reflect(std::move(values)); +} + +MULTISTR_TEST(BoostJsonTest, BasicTypesReflection, R"( +{{ json.bool | pprint }} +{{ json.small_int | pprint }} +{{ json.big_int | pprint }} +{{ json.double | pprint }} +{{ json.string | pprint }} +)", +R"( +true +100500 +100500100500100 +100.5 +'Hello World!' +)") +{ + boost::json::value values = { + {"bool", true}, + {"small_int", 100500}, + {"big_int", 100500100500100LL}, + {"double", 100.5}, + {"string", "Hello World!"}, + }; + + params["json"] = jinja2::Reflect(std::move(values)); +} + +MULTISTR_TEST(BoostJsonTest, BasicValuesReflection, R"( +{{ bool_val | pprint }} +{{ small_int_val | pprint }} +{{ big_int_val | pprint }} +{{ double_val | pprint }} +{{ string_val | pprint }} +{{ array_val | pprint }} +{{ object_val | pprint }} +{{ empty_val | pprint }} +)", + R"( +true +100500 +100500100500100 +100.5 +'Hello World!' +[1, 2, 3, 4] +{'message': 'Hello World from Parser!', 'message2': 'Hello World from Parser-123!'} +none +)") +{ + boost::json::value values = { + {"bool", true}, + {"small_int", 100500}, + {"big_int", 100500100500100LL}, + {"double", 100.5}, + {"string", "Hello World!"}, + {"array", {1, 2, 3, 4}}, + {"object", { + {"message", "Hello World from Parser!"}, + {"message2", "Hello World from Parser-123!"} + }}}; + + auto boolVal = values.at("bool"); + auto smallIntVal = values.at("small_int"); + auto bigIntVal = values.at("big_int"); + auto doubleVal = values.at("double"); + auto stringVal = values.at("string"); + auto arrayVal = values.at("array"); + auto objectVal = values.at("object"); + boost::json::value emptyVal; + + params["bool_val"] = jinja2::Reflect(std::move(boolVal)); + params["small_int_val"] = jinja2::Reflect(std::move(smallIntVal)); + params["big_int_val"] = jinja2::Reflect(std::move(bigIntVal)); + params["double_val"] = jinja2::Reflect(std::move(doubleVal)); + params["string_val"] = jinja2::Reflect(std::move(stringVal)); + params["array_val"] = jinja2::Reflect(std::move(arrayVal)); + params["object_val"] = jinja2::Reflect(std::move(objectVal)); + params["empty_val"] = jinja2::Reflect(std::move(emptyVal)); +} + +MULTISTR_TEST(BoostJsonTest, SubobjectReflection, +R"( +{{ json.object.message }} +{{ json.object.message3 }} +{{ json.object | list | join(', ') }} +)", +R"( +Hello World from Parser! + +message, message2 +)") +{ + boost::json::value values = { + {"object", { + {"message", "Hello World from Parser!"}, + {"message2", "Hello World from Parser-123!"} + }} + }; + + params["json"] = jinja2::Reflect(std::move(values)); +} + +MULTISTR_TEST(BoostJsonTest, ArrayReflection, +R"( +{{ json.array | sort | pprint }} +{{ json.array | length }} +{{ json.array | first }} +{{ json.array | last }} +{{ json.array[8] }}-{{ json.array[6] }}-{{ json.array[4] }} +)", +R"( +[1, 2, 3, 4, 5, 6, 7, 8, 9] +9 +9 +1 +1-3-5 +)") +{ + boost::json::value values = { + {"array", {9, 8, 7, 6, 5, 4, 3, 2, 1}} + }; + + params["json"] = jinja2::Reflect(std::move(values)); +} + +MULTISTR_TEST(BoostJsonTest, ParsedTypesReflection, R"( +{{ json.bool | pprint }} +{{ json.small_int | pprint }} +{{ json.big_int | pprint }} +{{ json.double | pprint }} +{{ json.string | pprint }} +{{ json.object.message | pprint }} +{{ json.array | sort | pprint }} +)", + R"( +true +100500 +100500100500100 +100.5 +'Hello World!' +'Hello World from Parser!' +[1, 2, 3, 4, 5, 6, 7, 8, 9] +)") +{ + boost::json::value values = boost::json::parse(R"( +{ + "big_int": 100500100500100, + "bool": true, + "double": 100.5, + "small_int": 100500, + "string": "Hello World!", + "object": {"message": "Hello World from Parser!"}, + "array": [9, 8, 7, 6, 5, 4, 3, 2, 1] +} +)");; + + params["json"] = jinja2::Reflect(std::move(values)); +} + diff --git a/test/nlohmann_json_binding_test.cpp b/test/binding/nlohmann_json_binding_test.cpp similarity index 98% rename from test/nlohmann_json_binding_test.cpp rename to test/binding/nlohmann_json_binding_test.cpp index 82227711..7942b65e 100644 --- a/test/nlohmann_json_binding_test.cpp +++ b/test/binding/nlohmann_json_binding_test.cpp @@ -1,11 +1,12 @@ #include #include -#include "gtest/gtest.h" +#include "../test_tools.h" +#include #include #include -#include "test_tools.h" + using NlohmannJsonTest = BasicTemplateRenderer; diff --git a/test/rapid_json_binding_test.cpp b/test/binding/rapid_json_binding_test.cpp similarity index 98% rename from test/rapid_json_binding_test.cpp rename to test/binding/rapid_json_binding_test.cpp index 189f3e72..d86f2f2e 100644 --- a/test/rapid_json_binding_test.cpp +++ b/test/binding/rapid_json_binding_test.cpp @@ -1,11 +1,12 @@ #include #include -#include "gtest/gtest.h" +#include "../test_tools.h" +#include #include #include -#include "test_tools.h" + class RapidJsonTest : public BasicTemplateRenderer { diff --git a/test/rapid_json_serializer_test.cpp b/test/binding/rapid_json_serializer_test.cpp similarity index 96% rename from test/rapid_json_serializer_test.cpp rename to test/binding/rapid_json_serializer_test.cpp index 8f9b441b..3cc0fb94 100644 --- a/test/rapid_json_serializer_test.cpp +++ b/test/binding/rapid_json_serializer_test.cpp @@ -1,7 +1,9 @@ #include + #ifndef JINJA2CPP_SHARED_LIB -#include "../src/rapid_json_serializer.h" -#include "gtest/gtest.h" + +#include "../../src/binding/rapid_json_serializer.h" +#include namespace { @@ -66,4 +68,4 @@ TEST(RapidJsonSerializerTest, SerializeComplexTypesWithIndention) EXPECT_EQ(indentedDocument, jsonValue.AsString(4)); } -#endif \ No newline at end of file +#endif diff --git a/thirdparty/CMakeLists.txt b/thirdparty/CMakeLists.txt index 77794b96..a57c5b2a 100644 --- a/thirdparty/CMakeLists.txt +++ b/thirdparty/CMakeLists.txt @@ -68,7 +68,7 @@ endif() if (NOT DEFINED JINJA2_PRIVATE_LIBS_INT) set(JINJA2CPP_PRIVATE_LIBS ${JINJA2CPP_PRIVATE_LIBS} Boost::variant - Boost::filesystem Boost::algorithm Boost::lexical_cast fmt RapidJson) + Boost::filesystem Boost::algorithm Boost::lexical_cast Boost::json fmt RapidJson) else () set (JINJA2CPP_PRIVATE_LIBS ${JINJA2_PRIVATE_LIBS_INT}) endif () diff --git a/thirdparty/boost b/thirdparty/boost index 9d3f9bcd..32da69a3 160000 --- a/thirdparty/boost +++ b/thirdparty/boost @@ -1 +1 @@ -Subproject commit 9d3f9bcd7d416880d4631d7d39cceeb4e8f25da0 +Subproject commit 32da69a36f84c5255af8a994951918c258bac601 diff --git a/thirdparty/external_boost_deps.cmake b/thirdparty/external_boost_deps.cmake index e61e621e..cd2f7e0b 100644 --- a/thirdparty/external_boost_deps.cmake +++ b/thirdparty/external_boost_deps.cmake @@ -19,31 +19,35 @@ if (MSVC) endif () endif () -find_package(boost_filesystem ${FIND_BOOST_PACKAGE_QUIET}) find_package(boost_algorithm ${FIND_BOOST_PACKAGE_QUIET}) -find_package(boost_variant ${FIND_BOOST_PACKAGE_QUIET}) +find_package(boost_filesystem ${FIND_BOOST_PACKAGE_QUIET}) +find_package(boost_json ${FIND_BOOST_PACKAGE_QUIET}) find_package(boost_optional ${FIND_BOOST_PACKAGE_QUIET}) +find_package(boost_variant ${FIND_BOOST_PACKAGE_QUIET}) -if(boost_filesystem_FOUND AND - boost_algorithm_FOUND AND - boost_variant_FOUND AND - boost_optional_FOUND) - imported_target_alias(boost_filesystem ALIAS boost_filesystem::boost_filesystem) +if (boost_algorithm_FOUND AND + boost_filesystem_FOUND AND + boost_json_FOUND AND + boost_optional_FOUND AND + boost_variant_FOUND) imported_target_alias(boost_algorithm ALIAS boost_algorithm::boost_algorithm) - imported_target_alias(boost_variant ALIAS boost_variant::boost_variant) + imported_target_alias(boost_filesystem ALIAS boost_filesystem::boost_filesystem) + imported_target_alias(boost_json ALIAS boost_json::boost_json) imported_target_alias(boost_optional ALIAS boost_optional::boost_optional) -else() - find_package(Boost COMPONENTS system filesystem ${FIND_BOOST_PACKAGE_QUIET} REQUIRED) + imported_target_alias(boost_variant ALIAS boost_variant::boost_variant) +else () + find_package(Boost COMPONENTS system filesystem json ${FIND_BOOST_PACKAGE_QUIET} REQUIRED) if (Boost_FOUND) - imported_target_alias(boost_filesystem ALIAS Boost::filesystem) imported_target_alias(boost_algorithm ALIAS Boost::boost) - imported_target_alias(boost_variant ALIAS Boost::boost) + imported_target_alias(boost_filesystem ALIAS Boost::filesystem) + imported_target_alias(boost_json ALIAS Boost::json) imported_target_alias(boost_optional ALIAS Boost::boost) + imported_target_alias(boost_variant ALIAS Boost::boost) endif () endif () -install(TARGETS boost_filesystem boost_algorithm boost_variant boost_optional +install(TARGETS boost_algorithm boost_filesystem boost_json boost_optional boost_variant EXPORT InstallTargets RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR} LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR} diff --git a/thirdparty/fmtlib b/thirdparty/fmtlib index bdfbd794..e29c2bc6 160000 --- a/thirdparty/fmtlib +++ b/thirdparty/fmtlib @@ -1 +1 @@ -Subproject commit bdfbd794e32ed8307c9d154f28f1fae9819edd5c +Subproject commit e29c2bc60ee8d934431e51b326d7d5ee6eb5c286 diff --git a/thirdparty/json/nlohmann b/thirdparty/json/nlohmann index 350ff4f7..4f8fba14 160000 --- a/thirdparty/json/nlohmann +++ b/thirdparty/json/nlohmann @@ -1 +1 @@ -Subproject commit 350ff4f7ced7c4117eae2fb93df02823c8021fcb +Subproject commit 4f8fba14066156b73f1189a2b8bd568bde5284c5 diff --git a/thirdparty/json/rapid b/thirdparty/json/rapid index 48fbd8cd..232389d4 160000 --- a/thirdparty/json/rapid +++ b/thirdparty/json/rapid @@ -1 +1 @@ -Subproject commit 48fbd8cd202ca54031fe799db2ad44ffa8e77c13 +Subproject commit 232389d4f1012dddec4ef84861face2d2ba85709 diff --git a/thirdparty/thirdparty-conan-build.cmake b/thirdparty/thirdparty-conan-build.cmake index ec1e2c4e..e465b68d 100644 --- a/thirdparty/thirdparty-conan-build.cmake +++ b/thirdparty/thirdparty-conan-build.cmake @@ -6,10 +6,10 @@ find_package(optional-lite REQUIRED) find_package(string-view-lite REQUIRED) find_package(nlohmann_json REQUIRED) -find_package(Boost) -set(CONAN_BOOST_PACKAGE_NAME Boost::Boost) -find_package(fmt) -find_package(RapidJSON) +find_package(Boost COMPONENTS algorithm filesystem json optional variant REQUIRED) +set(CONAN_BOOST_PACKAGE_NAME Boost::boost) +find_package(fmt REQUIRED) +find_package(rapidjson REQUIRED) -set(JINJA2_PRIVATE_LIBS_INT ${CONAN_BOOST_PACKAGE_NAME} fmt::fmt RapidJSON::RapidJSON nlohmann_json::nlohmann_json) +set(JINJA2_PRIVATE_LIBS_INT ${CONAN_BOOST_PACKAGE_NAME} fmt::fmt rapidjson::rapidjson nlohmann_json::nlohmann_json) set(JINJA2_PUBLIC_LIBS_INT nonstd::expected-lite nonstd::variant-lite nonstd::optional-lite nonstd::string-view-lite) diff --git a/thirdparty/thirdparty-external-boost.cmake b/thirdparty/thirdparty-external-boost.cmake index 019e9e8c..81a56cbe 100644 --- a/thirdparty/thirdparty-external-boost.cmake +++ b/thirdparty/thirdparty-external-boost.cmake @@ -1,4 +1,4 @@ -message(STATUS "'extnernal-boost' dependencies mode selected for Jinja2Cpp. All dependencies are used as submodules except of boost") +message(STATUS "'external-boost' dependencies mode selected for Jinja2Cpp. All dependencies are used as submodules except of boost") include (./thirdparty/internal_deps.cmake) include (./thirdparty/external_boost_deps.cmake) diff --git a/thirdparty/thirdparty-internal.cmake b/thirdparty/thirdparty-internal.cmake index 08cd33f2..22af186c 100644 --- a/thirdparty/thirdparty-internal.cmake +++ b/thirdparty/thirdparty-internal.cmake @@ -2,7 +2,6 @@ message(STATUS "'internal' dependencies mode selected for Jinja2Cpp. All depende include (./thirdparty/internal_deps.cmake) -update_submodule(boost) set(BOOST_ENABLE_CMAKE ON) list(APPEND BOOST_INCLUDE_LIBRARIES algorithm @@ -12,9 +11,17 @@ list(APPEND BOOST_INCLUDE_LIBRARIES lexical_cast optional variant + json ) -set(BOOST_INCLUDE_LIBRARIES ${BOOST_INCLUDE_LIBRARIES} CACHE INTERNAL "") -add_subdirectory(thirdparty/boost) + +include(FetchContent) +FetchContent_Declare( + Boost + GIT_REPOSITORY https://github.com/boostorg/boost.git + GIT_TAG boost-1.80.0 + PATCH_COMMAND git apply --ignore-whitespace "${CMAKE_CURRENT_LIST_DIR}/../cmake/patches/0001-fix-skip-install-rules.patch" || true +) +FetchContent_MakeAvailable(Boost) if(NOT MSVC) # Enable -Werror and -Wall on jinja2cpp target, ignoring warning errors from thirdparty libs @@ -27,6 +34,7 @@ if(NOT MSVC) target_compile_options(boost_assert INTERFACE -Wno-error=parentheses) endif() if(COMPILER_HAS_WNO_ERROR_DEPRECATED_DECLARATIONS_FLAG) + target_compile_options(boost_unordered INTERFACE -Wno-error=deprecated-declarations) target_compile_options(boost_filesystem PRIVATE -Wno-error=deprecated-declarations) endif() if(COMPILER_HAS_WNO_ERROR_MAYBE_UNINITIALIZED_FLAG) @@ -35,7 +43,7 @@ if(NOT MSVC) else () endif() -# install(TARGETS boost_filesystem boost::algorithm boost::variant boost::optional +# install(TARGETS boost_filesystem boost_algorithm boost_variant boost_optional # EXPORT InstallTargets # RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR} # LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR} From 94636707a545413b71e08d630e71440c955d4424 Mon Sep 17 00:00:00 2001 From: rmorozov Date: Mon, 12 Dec 2022 10:05:03 +0300 Subject: [PATCH 05/14] update GitHub actions to git rid of nodejs12 warning & update some deps (#231) * update github actions * update json deps and fmtlib * add makefile verbosity in linux build --- .github/workflows/codeql-analysis.yml | 4 ++-- .github/workflows/linux-build.yml | 4 ++-- .github/workflows/windows-build.yml | 2 +- .gitmodules | 3 --- thirdparty/boost | 1 - thirdparty/fmtlib | 2 +- thirdparty/json/nlohmann | 2 +- thirdparty/json/rapid | 2 +- 8 files changed, 8 insertions(+), 12 deletions(-) delete mode 160000 thirdparty/boost diff --git a/.github/workflows/codeql-analysis.yml b/.github/workflows/codeql-analysis.yml index bfed1c63..04e8819e 100644 --- a/.github/workflows/codeql-analysis.yml +++ b/.github/workflows/codeql-analysis.yml @@ -38,11 +38,11 @@ jobs: steps: - name: Checkout repository - uses: actions/checkout@v2 + uses: actions/checkout@v3 # Initializes the CodeQL tools for scanning. - name: Initialize CodeQL - uses: github/codeql-action/init@v1 + uses: github/codeql-action/init@v2 with: languages: ${{ matrix.language }} # If you wish to specify custom queries, you can do so here or in a config file. diff --git a/.github/workflows/linux-build.yml b/.github/workflows/linux-build.yml index 7e283b24..769ad56e 100644 --- a/.github/workflows/linux-build.yml +++ b/.github/workflows/linux-build.yml @@ -40,7 +40,7 @@ jobs: extra-flags: -DJINJA2CPP_STRICT_WARNINGS=OFF steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v3 - name: Setup environment env: INPUT_COMPILER: ${{ matrix.compiler }} @@ -75,7 +75,7 @@ jobs: run: | set -ex export BUILD_TARGET=all - export CMAKE_OPTS=-DCMAKE_VERBOSE_MAKEFILE=OFF + export CMAKE_OPTS=-DCMAKE_VERBOSE_MAKEFILE=ON if [[ "${INPUT_COMPILER}" != "" ]]; then export CXX=${INPUT_COMPILER}; fi export BUILD_CONFIG=${INPUT_BASE_CONFIG} $CXX --version diff --git a/.github/workflows/windows-build.yml b/.github/workflows/windows-build.yml index 1204e857..b7a4635f 100644 --- a/.github/workflows/windows-build.yml +++ b/.github/workflows/windows-build.yml @@ -46,7 +46,7 @@ jobs: steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v3 - name: Build shell: cmd diff --git a/.gitmodules b/.gitmodules index 0b3e6045..35685d99 100644 --- a/.gitmodules +++ b/.gitmodules @@ -1,9 +1,6 @@ [submodule "thirdparty/gtest"] path = thirdparty/gtest url = https://github.com/google/googletest.git -#[submodule "thirdparty/boost"] -# path = thirdparty/boost -# url = https://github.com/boostorg/boost.git [submodule "thirdparty/nonstd/expected-lite"] path = thirdparty/nonstd/expected-lite url = https://github.com/martinmoene/expected-lite.git diff --git a/thirdparty/boost b/thirdparty/boost deleted file mode 160000 index 32da69a3..00000000 --- a/thirdparty/boost +++ /dev/null @@ -1 +0,0 @@ -Subproject commit 32da69a36f84c5255af8a994951918c258bac601 diff --git a/thirdparty/fmtlib b/thirdparty/fmtlib index e29c2bc6..a3370119 160000 --- a/thirdparty/fmtlib +++ b/thirdparty/fmtlib @@ -1 +1 @@ -Subproject commit e29c2bc60ee8d934431e51b326d7d5ee6eb5c286 +Subproject commit a33701196adfad74917046096bf5a2aa0ab0bb50 diff --git a/thirdparty/json/nlohmann b/thirdparty/json/nlohmann index 4f8fba14..bc889afb 160000 --- a/thirdparty/json/nlohmann +++ b/thirdparty/json/nlohmann @@ -1 +1 @@ -Subproject commit 4f8fba14066156b73f1189a2b8bd568bde5284c5 +Subproject commit bc889afb4c5bf1c0d8ee29ef35eaaf4c8bef8a5d diff --git a/thirdparty/json/rapid b/thirdparty/json/rapid index 232389d4..80b6d1c8 160000 --- a/thirdparty/json/rapid +++ b/thirdparty/json/rapid @@ -1 +1 @@ -Subproject commit 232389d4f1012dddec4ef84861face2d2ba85709 +Subproject commit 80b6d1c83402a5785c486603c5611923159d0894 From 92a8ab88105557ec1cc2be34193d0baa23a04614 Mon Sep 17 00:00:00 2001 From: Ruslan Morozov Date: Sat, 17 Dec 2022 20:25:05 +0300 Subject: [PATCH 06/14] fix #233 install preserving directory structure unfortunately to fix it using cmake-native mechanics it required to bump cmake version to 3.23.0 --- CMakeLists.txt | 50 ++++++++++++++++++---------------- thirdparty/internal_deps.cmake | 4 --- 2 files changed, 27 insertions(+), 27 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 51872780..6f66eb0d 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,5 +1,5 @@ -cmake_minimum_required(VERSION 3.0.1) -project(Jinja2Cpp VERSION 1.1.0) +cmake_minimum_required(VERSION 3.23.0) +project(Jinja2Cpp VERSION 1.2.2) if (${CMAKE_VERSION} VERSION_GREATER "3.12") cmake_policy(SET CMP0074 OLD) @@ -148,6 +148,12 @@ add_library(${LIB_TARGET_NAME} ${LIB_LINK_TYPE} ${PublicHeaders} ) +target_sources(${LIB_TARGET_NAME} + PUBLIC FILE_SET HEADERS + FILES ${PublicHeaders} + BASE_DIRS include +) + string(TOUPPER "${CMAKE_BUILD_TYPE}" BUILD_CFG_NAME) set(CURRENT_CXX_FLAGS "${CMAKE_CXX_FLAGS} ${CMAKE_CXX_FLAGS_${BUILD_CFG_NAME}}") @@ -156,17 +162,17 @@ set(JINJA2CPP_PUBLIC_LIBS "${JINJA2CPP_EXTRA_LIBS}") separate_arguments(JINJA2CPP_PUBLIC_LIBS) if (JINJA2CPP_WITH_COVERAGE) target_compile_options( - ${JINJA2CPP_COVERAGE_TARGET} - INTERFACE - -g -O0 + ${JINJA2CPP_COVERAGE_TARGET} + INTERFACE + -g -O0 ) list(APPEND JINJA2CPP_PUBLIC_LIBS ${JINJA2CPP_COVERAGE_TARGET}) endif() if (NOT JINJA2CPP_WITH_SANITIZERS STREQUAL "none") target_compile_options( - ${JINJA2CPP_SANITIZE_TARGET} - INTERFACE - -g -O2 + ${JINJA2CPP_SANITIZE_TARGET} + INTERFACE + -g -O2 ) list(APPEND JINJA2CPP_PUBLIC_LIBS ${JINJA2CPP_SANITIZE_TARGET}) endif() @@ -186,7 +192,7 @@ target_include_directories(${LIB_TARGET_NAME} PUBLIC $ $ - ) +) if (JINJA2CPP_STRICT_WARNINGS) if (UNIX) @@ -211,16 +217,14 @@ if (JINJA2CPP_BUILD_SHARED) endif () set_target_properties(${LIB_TARGET_NAME} PROPERTIES - VERSION ${PROJECT_VERSION} - SOVERSION 1 - ) + VERSION ${PROJECT_VERSION} + SOVERSION 1 +) set_target_properties(${LIB_TARGET_NAME} PROPERTIES - CXX_STANDARD ${JINJA2CPP_CXX_STANDARD} - CXX_STANDARD_REQUIRED ON - ) - -set_property(TARGET ${LIB_TARGET_NAME} PROPERTY PUBLIC_HEADER ${PublicHeaders} ${JINJA2CPP_EXTRA_PUBLIC_HEADERS}) + CXX_STANDARD ${JINJA2CPP_CXX_STANDARD} + CXX_STANDARD_REQUIRED ON +) configure_file(jinja2cpp.pc.in ${CMAKE_BINARY_DIR}/jinja2cpp.pc @ONLY) @@ -241,14 +245,14 @@ if (JINJA2CPP_BUILD_TESTS) endif () add_custom_command( - OUTPUT ${CMAKE_CURRENT_BINARY_DIR}/test_data/simple_template1.j2tpl - COMMAND ${CMAKE_COMMAND} ARGS -E copy_directory ${CMAKE_CURRENT_SOURCE_DIR}/test/test_data ${CMAKE_CURRENT_BINARY_DIR}/test_data - MAIN_DEPENDENCY ${CMAKE_CURRENT_SOURCE_DIR}/test/test_data/simple_template1.j2tpl - COMMENT "Copy test data to the destination dir" + OUTPUT ${CMAKE_CURRENT_BINARY_DIR}/test_data/simple_template1.j2tpl + COMMAND ${CMAKE_COMMAND} ARGS -E copy_directory ${CMAKE_CURRENT_SOURCE_DIR}/test/test_data ${CMAKE_CURRENT_BINARY_DIR}/test_data + MAIN_DEPENDENCY ${CMAKE_CURRENT_SOURCE_DIR}/test/test_data/simple_template1.j2tpl + COMMENT "Copy test data to the destination dir" ) add_custom_target(CopyTestData ALL - DEPENDS ${CMAKE_CURRENT_BINARY_DIR}/test_data/simple_template1.j2tpl + DEPENDS ${CMAKE_CURRENT_BINARY_DIR}/test_data/simple_template1.j2tpl ) add_dependencies(jinja2cpp_tests CopyTestData) @@ -289,7 +293,7 @@ install( RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR} LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR} ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR}/static - PUBLIC_HEADER DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}/jinja2cpp + FILE_SET HEADERS ) install( diff --git a/thirdparty/internal_deps.cmake b/thirdparty/internal_deps.cmake index 3d6640ae..43afe2cb 100644 --- a/thirdparty/internal_deps.cmake +++ b/thirdparty/internal_deps.cmake @@ -3,20 +3,16 @@ add_subdirectory(thirdparty/nonstd/expected-lite EXCLUDE_FROM_ALL) update_submodule(nonstd/variant-lite) add_subdirectory(thirdparty/nonstd/variant-lite EXCLUDE_FROM_ALL) -add_library(variant-lite ALIAS variant-lite) update_submodule(nonstd/optional-lite) add_subdirectory(thirdparty/nonstd/optional-lite EXCLUDE_FROM_ALL) -add_library(optional-lite ALIAS optional-lite) update_submodule(nonstd/string-view-lite) add_subdirectory(thirdparty/nonstd/string-view-lite EXCLUDE_FROM_ALL) -add_library(string-view-lite ALIAS string-view-lite) update_submodule(fmtlib) set (FMT_INSTALL ON CACHE BOOL "" FORCE) add_subdirectory(thirdparty/fmtlib EXCLUDE_FROM_ALL) -add_library(fmt ALIAS fmt-header-only) update_submodule(json/rapid) set (RAPIDJSON_BUILD_DOC OFF CACHE BOOL "" FORCE) From 3fc48f35f01f4577bed7e854fc8c4b68c81c9799 Mon Sep 17 00:00:00 2001 From: Chad Condon Date: Mon, 27 Feb 2023 10:51:02 -0800 Subject: [PATCH 07/14] Fix CMake find_package --- cmake/public/jinja2cpp-config.cmake.in | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cmake/public/jinja2cpp-config.cmake.in b/cmake/public/jinja2cpp-config.cmake.in index 67cfde85..a437f2a9 100644 --- a/cmake/public/jinja2cpp-config.cmake.in +++ b/cmake/public/jinja2cpp-config.cmake.in @@ -59,7 +59,7 @@ set_target_properties(jinja2cpp PROPERTIES if (JINJA2CPP_BUILD_SHARED) target_compile_definitions(jinja2cpp PUBLIC -DJINJA2CPP_LINK_AS_SHARED) -else() +endif() # INTERFACE_LINK_LIBRARIES "nonstd::expected-lite;nonstd::variant-lite;nonstd::value_ptr-lite;nonstd::optional-lite;\$;\$;\$" From b776a46072ece63ccc684c9d33a63666e81a2c19 Mon Sep 17 00:00:00 2001 From: Ruslan Morozov Date: Fri, 2 Jun 2023 21:27:05 +0300 Subject: [PATCH 08/14] fix #235 mutable reference to vector of variants triggered a situation when destructor cleared a global var after template rendering add a test for regression --- src/template_impl.h | 2 +- test/basic_tests.cpp | 24 +++++++++++++++++++++++- 2 files changed, 24 insertions(+), 2 deletions(-) diff --git a/src/template_impl.h b/src/template_impl.h index a84e60fe..8f4ad3a6 100644 --- a/src/template_impl.h +++ b/src/template_impl.h @@ -251,7 +251,7 @@ class TemplateImpl : public ITemplateImpl InternalValueMap extParams; InternalValueMap intParams; - auto convertFn = [&intParams](auto& params) { + auto convertFn = [&intParams](const auto& params) { for (auto& ip : params) { auto valRef = &ip.second.data(); diff --git a/test/basic_tests.cpp b/test/basic_tests.cpp index 05823ed8..e9e53ddf 100644 --- a/test/basic_tests.cpp +++ b/test/basic_tests.cpp @@ -645,6 +645,28 @@ from Parser!)"; EXPECT_STREQ(expectedResult.c_str(), result.c_str()); } +TEST(BasicTests, EnvTestPreservesGlobalVar) +{ + jinja2::TemplateEnv tplEnv; + tplEnv.AddGlobal("global_var", jinja2::Value("foo")); + tplEnv.AddGlobal("global_fn", jinja2::MakeCallable([]() { + return "bar"; + })); + std::string result1; + { + jinja2::Template tpl(&tplEnv); + tpl.Load("Hello {{ global_var }} {{ global_fn() }}!!!"); + result1 = tpl.RenderAsString(jinja2::ValuesMap{}).value(); + } + std::string result2; + { + jinja2::Template tpl(&tplEnv); + tpl.Load("Hello {{ global_var }} {{ global_fn() }}!!!"); + result2 = tpl.RenderAsString(jinja2::ValuesMap{}).value(); + } + ASSERT_EQ(result1, result2); +} + MULTISTR_TEST(BasicMultiStrTest, LiteralWithEscapeCharacters, R"({{ 'Hello\t\nWorld\n\twith\nescape\tcharacters!' }})", "Hello\t\nWorld\n\twith\nescape\tcharacters!") { -} \ No newline at end of file +} From 05014bf67529de2b4140b3dfff6481ef5d911712 Mon Sep 17 00:00:00 2001 From: rmorozov Date: Sat, 3 Jun 2023 13:39:34 +0300 Subject: [PATCH 09/14] bump deps (#240) * bump deps * boost 1.82.0 * rapid json * -lite libs * google test(it seems latest version targeting c++14) * robinhood hashing * fmt 10.0.0 * fix boost patch --- .../patches/0001-fix-skip-install-rules.patch | 16 +++---- src/expression_evaluator.cpp | 4 +- src/robin_hood.h | 43 +++++++++++++------ src/template_parser.cpp | 7 +-- src/value_visitors.h | 2 +- thirdparty/fmtlib | 2 +- thirdparty/gtest | 2 +- thirdparty/json/rapid | 2 +- thirdparty/nonstd/expected-lite | 2 +- thirdparty/nonstd/optional-lite | 2 +- thirdparty/nonstd/string-view-lite | 2 +- thirdparty/nonstd/variant-lite | 2 +- thirdparty/thirdparty-internal.cmake | 2 +- 13 files changed, 53 insertions(+), 35 deletions(-) diff --git a/cmake/patches/0001-fix-skip-install-rules.patch b/cmake/patches/0001-fix-skip-install-rules.patch index caa4299c..d20445a1 100644 --- a/cmake/patches/0001-fix-skip-install-rules.patch +++ b/cmake/patches/0001-fix-skip-install-rules.patch @@ -1,25 +1,25 @@ -From 857dc932fc32d3a1d30abac7724565812b446cd2 Mon Sep 17 00:00:00 2001 +From d924c3bf4d83e9ef3ce66a6ac1e80ef1cb7cc4ca Mon Sep 17 00:00:00 2001 From: Ruslan Morozov -Date: Thu, 8 Dec 2022 21:11:49 +0300 -Subject: [PATCH] fix skip install rules +Date: Sat, 3 Jun 2023 12:21:15 +0300 +Subject: [PATCH] [PATCH] fix skip install rules --- include/BoostRoot.cmake | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tools/cmake/include/BoostRoot.cmake b/tools/cmake/include/BoostRoot.cmake -index f2a51e1..a37f14d 100644 +index e93f907..f0380b3 100644 --- a/tools/cmake/include/BoostRoot.cmake +++ b/tools/cmake/include/BoostRoot.cmake -@@ -90,7 +90,7 @@ else() +@@ -108,7 +108,7 @@ else() endif() set(BUILD_TESTING OFF) -- set(CMAKE_SKIP_INSTALL_RULES ON) -+ set(CMAKE_SKIP_INSTALL_RULES OFF) +- set(BOOST_SKIP_INSTALL_RULES ON) ++ set(BOOST_SKIP_INSTALL_RULES OFF) endif() -- -2.37.1 (Apple Git-137.1) +2.39.2 (Apple Git-143) diff --git a/src/expression_evaluator.cpp b/src/expression_evaluator.cpp index da8618cb..136e33d8 100644 --- a/src/expression_evaluator.cpp +++ b/src/expression_evaluator.cpp @@ -424,6 +424,8 @@ Result ParseCallParamsImpl(const T& args, const P& params, bool& isSucceeded) int firstMandatoryIdx = -1; int prevNotFound = -1; int foundKwArgs = 0; + (void)foundKwArgs; // extremely odd bug in clang warning + // Wunused-but-set-variable // Find all provided keyword args for (auto& argInfo : args) @@ -441,7 +443,7 @@ Result ParseCallParamsImpl(const T& args, const P& params, bool& isSucceeded) { result.args[argInfo.name] = p->second; argsInfo[argIdx].state = Keyword; - ++ foundKwArgs; + ++foundKwArgs; } else { diff --git a/src/robin_hood.h b/src/robin_hood.h index 511a308d..b4e0fbc5 100644 --- a/src/robin_hood.h +++ b/src/robin_hood.h @@ -36,7 +36,7 @@ // see https://semver.org/ #define ROBIN_HOOD_VERSION_MAJOR 3 // for incompatible API changes #define ROBIN_HOOD_VERSION_MINOR 11 // for adding functionality in a backwards-compatible manner -#define ROBIN_HOOD_VERSION_PATCH 3 // for backwards-compatible bug fixes +#define ROBIN_HOOD_VERSION_PATCH 5 // for backwards-compatible bug fixes #include #include @@ -206,7 +206,7 @@ static Counts& counts() { // workaround missing "is_trivially_copyable" in g++ < 5.0 // See https://stackoverflow.com/a/31798726/48181 -#if defined(__GNUC__) && __GNUC__ < 5 +#if defined(__GNUC__) && __GNUC__ < 5 && !defined(__clang__) # define ROBIN_HOOD_IS_TRIVIALLY_COPYABLE(...) __has_trivial_copy(__VA_ARGS__) #else # define ROBIN_HOOD_IS_TRIVIALLY_COPYABLE(...) std::is_trivially_copyable<__VA_ARGS__>::value @@ -1820,6 +1820,12 @@ class Table InsertionState::key_found != idxAndState.second); } + template + iterator emplace_hint(const_iterator position, Args&&... args) { + (void)position; + return emplace(std::forward(args)...).first; + } + template std::pair try_emplace(const key_type& key, Args&&... args) { return try_emplace_impl(key, std::forward(args)...); @@ -1831,16 +1837,15 @@ class Table } template - std::pair try_emplace(const_iterator hint, const key_type& key, - Args&&... args) { + iterator try_emplace(const_iterator hint, const key_type& key, Args&&... args) { (void)hint; - return try_emplace_impl(key, std::forward(args)...); + return try_emplace_impl(key, std::forward(args)...).first; } template - std::pair try_emplace(const_iterator hint, key_type&& key, Args&&... args) { + iterator try_emplace(const_iterator hint, key_type&& key, Args&&... args) { (void)hint; - return try_emplace_impl(std::move(key), std::forward(args)...); + return try_emplace_impl(std::move(key), std::forward(args)...).first; } template @@ -1854,16 +1859,15 @@ class Table } template - std::pair insert_or_assign(const_iterator hint, const key_type& key, - Mapped&& obj) { + iterator insert_or_assign(const_iterator hint, const key_type& key, Mapped&& obj) { (void)hint; - return insertOrAssignImpl(key, std::forward(obj)); + return insertOrAssignImpl(key, std::forward(obj)).first; } template - std::pair insert_or_assign(const_iterator hint, key_type&& key, Mapped&& obj) { + iterator insert_or_assign(const_iterator hint, key_type&& key, Mapped&& obj) { (void)hint; - return insertOrAssignImpl(std::move(key), std::forward(obj)); + return insertOrAssignImpl(std::move(key), std::forward(obj)).first; } std::pair insert(const value_type& keyval) { @@ -1871,10 +1875,20 @@ class Table return emplace(keyval); } + iterator insert(const_iterator hint, const value_type& keyval) { + (void)hint; + return emplace(keyval).first; + } + std::pair insert(value_type&& keyval) { return emplace(std::move(keyval)); } + iterator insert(const_iterator hint, value_type&& keyval) { + (void)hint; + return emplace(std::move(keyval)).first; + } + // Returns 1 if key is found, 0 otherwise. size_t count(const key_type& key) const { // NOLINT(modernize-use-nodiscard) ROBIN_HOOD_TRACE(this) @@ -2308,13 +2322,14 @@ class Table auto const numElementsWithBuffer = calcNumElementsWithBuffer(max_elements); - // calloc also zeroes everything + // malloc & zero mInfo. Faster than calloc everything. auto const numBytesTotal = calcNumBytesTotal(numElementsWithBuffer); ROBIN_HOOD_LOG("std::calloc " << numBytesTotal << " = calcNumBytesTotal(" << numElementsWithBuffer << ")") mKeyVals = reinterpret_cast( - detail::assertNotNull(std::calloc(1, numBytesTotal))); + detail::assertNotNull(std::malloc(numBytesTotal))); mInfo = reinterpret_cast(mKeyVals + numElementsWithBuffer); + std::memset(mInfo, 0, numBytesTotal - numElementsWithBuffer * sizeof(Node)); // set sentinel mInfo[numElementsWithBuffer] = 1; diff --git a/src/template_parser.cpp b/src/template_parser.cpp index f715d9a7..bd090393 100644 --- a/src/template_parser.cpp +++ b/src/template_parser.cpp @@ -1,4 +1,5 @@ #include "template_parser.h" +#include "renderer.h" #include namespace jinja2 @@ -203,7 +204,7 @@ StatementsParser::ParseResult StatementsParser::ParseEndFor(LexScanner&, Stateme { auto r = std::static_pointer_cast(info.renderer); r->SetMainBody(info.compositions[0]); - elseRenderer = r; + elseRenderer = std::static_pointer_cast(r); statementsInfo.pop_back(); info = statementsInfo.back(); @@ -246,7 +247,7 @@ StatementsParser::ParseResult StatementsParser::ParseElse(LexScanner& /*lexer*/, { auto renderer = std::make_shared(ExpressionEvaluatorPtr<>()); StatementInfo statementInfo = StatementInfo::Create(StatementInfo::ElseIfStatement, stmtTok); - statementInfo.renderer = renderer; + statementInfo.renderer = std::static_pointer_cast(renderer); statementsInfo.push_back(statementInfo); return ParseResult(); } @@ -262,7 +263,7 @@ StatementsParser::ParseResult StatementsParser::ParseElIf(LexScanner& lexer, Sta auto renderer = std::make_shared(*valueExpr); StatementInfo statementInfo = StatementInfo::Create(StatementInfo::ElseIfStatement, stmtTok); - statementInfo.renderer = renderer; + statementInfo.renderer = std::static_pointer_cast(renderer); statementsInfo.push_back(statementInfo); return ParseResult(); } diff --git a/src/value_visitors.h b/src/value_visitors.h index 6157d292..4ac874f5 100644 --- a/src/value_visitors.h +++ b/src/value_visitors.h @@ -25,7 +25,7 @@ namespace detail template struct RecursiveUnwrapper { - V* m_visitor; + V* m_visitor{}; RecursiveUnwrapper(V* v) : m_visitor(v) diff --git a/thirdparty/fmtlib b/thirdparty/fmtlib index a3370119..a0b8a92e 160000 --- a/thirdparty/fmtlib +++ b/thirdparty/fmtlib @@ -1 +1 @@ -Subproject commit a33701196adfad74917046096bf5a2aa0ab0bb50 +Subproject commit a0b8a92e3d1532361c2f7feb63babc5c18d00ef2 diff --git a/thirdparty/gtest b/thirdparty/gtest index aefb4546..b796f7d4 160000 --- a/thirdparty/gtest +++ b/thirdparty/gtest @@ -1 +1 @@ -Subproject commit aefb45469ee7e6bde0cd1d2c18412046c30e7bb6 +Subproject commit b796f7d44681514f58a683a3a71ff17c94edb0c1 diff --git a/thirdparty/json/rapid b/thirdparty/json/rapid index 80b6d1c8..973dc9c0 160000 --- a/thirdparty/json/rapid +++ b/thirdparty/json/rapid @@ -1 +1 @@ -Subproject commit 80b6d1c83402a5785c486603c5611923159d0894 +Subproject commit 973dc9c06dcd3d035ebd039cfb9ea457721ec213 diff --git a/thirdparty/nonstd/expected-lite b/thirdparty/nonstd/expected-lite index 22977a2a..49af05a2 160000 --- a/thirdparty/nonstd/expected-lite +++ b/thirdparty/nonstd/expected-lite @@ -1 +1 @@ -Subproject commit 22977a2a0aad56614cde2a8a1f9525f18053e24c +Subproject commit 49af05a2fdda423f8aa3918c2b96ccfa1857c3dd diff --git a/thirdparty/nonstd/optional-lite b/thirdparty/nonstd/optional-lite index 0854335c..00e9cf5c 160000 --- a/thirdparty/nonstd/optional-lite +++ b/thirdparty/nonstd/optional-lite @@ -1 +1 @@ -Subproject commit 0854335c64461d07d00f85b068075ed8871859ec +Subproject commit 00e9cf5ca5a496e857bc6a28ffed9f4189ce6646 diff --git a/thirdparty/nonstd/string-view-lite b/thirdparty/nonstd/string-view-lite index d27d7b50..5b1d95fe 160000 --- a/thirdparty/nonstd/string-view-lite +++ b/thirdparty/nonstd/string-view-lite @@ -1 +1 @@ -Subproject commit d27d7b5081406a35b41cb16b321be8833b4cd811 +Subproject commit 5b1d95fe2c0ee18e654876487898b9a423a954db diff --git a/thirdparty/nonstd/variant-lite b/thirdparty/nonstd/variant-lite index 9499655b..5015e841 160000 --- a/thirdparty/nonstd/variant-lite +++ b/thirdparty/nonstd/variant-lite @@ -1 +1 @@ -Subproject commit 9499655b9c263eaef735efeeb53892c770d447e1 +Subproject commit 5015e841cf143487f2d7e2f619b618d455658fab diff --git a/thirdparty/thirdparty-internal.cmake b/thirdparty/thirdparty-internal.cmake index 22af186c..11de37ed 100644 --- a/thirdparty/thirdparty-internal.cmake +++ b/thirdparty/thirdparty-internal.cmake @@ -18,7 +18,7 @@ include(FetchContent) FetchContent_Declare( Boost GIT_REPOSITORY https://github.com/boostorg/boost.git - GIT_TAG boost-1.80.0 + GIT_TAG boost-1.82.0 PATCH_COMMAND git apply --ignore-whitespace "${CMAKE_CURRENT_LIST_DIR}/../cmake/patches/0001-fix-skip-install-rules.patch" || true ) FetchContent_MakeAvailable(Boost) From 3d782f15adfd636ded2015890a359dba4d54019e Mon Sep 17 00:00:00 2001 From: rmorozov Date: Fri, 16 Jun 2023 21:48:03 +0300 Subject: [PATCH 10/14] optimise template load time (#241) trying to fix #237 according to research https://github.com/HFTrader/regex-performance std::regex is extremely slow x times slower than boost one's ==> changing default regex engine to boost one's presering ability to build jinja2cpp with standard regex on the local PC speed up of using boost::regex instead of std::regex shows up to be 5-6x times 20s to 120s --- CMakeLists.txt | 13 ++++++++- src/template_parser.h | 43 +++++++++++++++++++--------- test/perf_test.cpp | 20 +++++++++++++ thirdparty/CMakeLists.txt | 2 +- thirdparty/external_boost_deps.cmake | 13 +++++++-- 5 files changed, 73 insertions(+), 18 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 6f66eb0d..0be30837 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -15,6 +15,9 @@ endif() set(JINJA2CPP_SANITIZERS address+undefined memory) set(JINJA2CPP_WITH_SANITIZERS none CACHE STRING "Build with sanitizer") set_property(CACHE JINJA2CPP_WITH_SANITIZERS PROPERTY STRINGS ${JINJA2CPP_SANITIZERS}) +set(JINJA2CPP_SUPPORTED_REGEX std boost) +set(JINJA2CPP_USE_REGEX boost CACHE STRING "Use regex parser in lexer, boost works faster on most platforms") +set_property(CACHE JINJA2CPP_USE_REGEX PROPERTY STRINGS ${JINJA2CPP_SUPPORTED_REGEX}) set(JINJA2CPP_WITH_JSON_BINDINGS boost nlohmann rapid all none) set(JINJA2CPP_WITH_JSON_BINDINGS all CACHE STRING "Build with json support") set_property(CACHE JINJA2CPP_WITH_JSON_BINDINGS PROPERTY STRINGS ${JINJA2CPP_WITH_JSON_BINDINGS}) @@ -210,7 +213,15 @@ elseif (CMAKE_CXX_COMPILER_ID MATCHES "MSVC") target_compile_options(${LIB_TARGET_NAME} PRIVATE ${MSVC_CXX_FLAGS}) endif () -target_compile_definitions(${LIB_TARGET_NAME} PUBLIC -DBOOST_SYSTEM_NO_DEPRECATED -DBOOST_ERROR_CODE_HEADER_ONLY) +if ("${JINJA2CPP_USE_REGEX}" STREQUAL "boost") + set(_regex_define "-DJINJA2CPP_USE_REGEX_BOOST") +endif() +target_compile_definitions(${LIB_TARGET_NAME} + PUBLIC + -DBOOST_SYSTEM_NO_DEPRECATED + -DBOOST_ERROR_CODE_HEADER_ONLY + ${_regex_define} +) if (JINJA2CPP_BUILD_SHARED) target_compile_definitions(${LIB_TARGET_NAME} PRIVATE -DJINJA2CPP_BUILD_AS_SHARED PUBLIC -DJINJA2CPP_LINK_AS_SHARED) diff --git a/src/template_parser.h b/src/template_parser.h index d993436d..c183f5b6 100644 --- a/src/template_parser.h +++ b/src/template_parser.h @@ -17,11 +17,28 @@ #include #include -#include #include #include #include +#ifdef JINJA2CPP_USE_REGEX_BOOST +#include +template +using BasicRegex = boost::basic_regex; +using Regex = boost::regex; +using WideRegex = boost::wregex; +template +using RegexIterator = boost::regex_iterator; +#else +#include +template +using BasicRegex = std::basic_regex; +using Regex = std::regex; +using WideRegex = std::wregex; +template +using RegexIterator = std::regex_iterator; +#endif + namespace jinja2 { template @@ -58,9 +75,9 @@ MultiStringLiteral ParserTraitsBase::s_regexp = UNIVERSAL_STR( template<> struct ParserTraits : public ParserTraitsBase<> { - static std::regex GetRoughTokenizer() - { return std::regex(s_regexp.GetValueStr()); } - static std::regex GetKeywords() + static Regex GetRoughTokenizer() + { return Regex(s_regexp.GetValueStr()); } + static Regex GetKeywords() { std::string pattern; std::string prefix("(^"); @@ -76,7 +93,7 @@ struct ParserTraits : public ParserTraitsBase<> pattern += prefix + info.name.charValue + postfix; } - return std::regex(pattern); + return Regex(pattern); } static std::string GetAsString(const std::string& str, CharRange range) { return str.substr(range.startOffset, range.size()); } static InternalValue RangeToNum(const std::string& str, CharRange range, Token::Type hint) @@ -109,9 +126,9 @@ struct ParserTraits : public ParserTraitsBase<> template<> struct ParserTraits : public ParserTraitsBase<> { - static std::wregex GetRoughTokenizer() - { return std::wregex(s_regexp.GetValueStr()); } - static std::wregex GetKeywords() + static WideRegex GetRoughTokenizer() + { return WideRegex(s_regexp.GetValueStr()); } + static WideRegex GetKeywords() { std::wstring pattern; std::wstring prefix(L"(^"); @@ -127,7 +144,7 @@ struct ParserTraits : public ParserTraitsBase<> pattern += prefix + info.name.wcharValue + postfix; } - return std::wregex(pattern); + return WideRegex(pattern); } static std::string GetAsString(const std::wstring& str, CharRange range) { @@ -248,7 +265,7 @@ class TemplateParser : public LexerHelper public: using string_t = std::basic_string; using traits_t = ParserTraits; - using sregex_iterator = std::regex_iterator; + using sregex_iterator = RegexIterator; using ErrorInfo = ErrorInfoTpl; using ParseResult = nonstd::expected>; @@ -437,7 +454,7 @@ class TemplateParser : public LexerHelper FinishCurrentLine(match.position() + 2); return MakeParseError(ErrorCode::UnexpectedCommentEnd, MakeToken(Token::CommentEnd, { matchStart, matchStart + 2 })); } - + m_currentBlockInfo.range.startOffset = FinishCurrentBlock(matchStart, TextBlockType::RawText); break; case RM_ExprBegin: @@ -925,8 +942,8 @@ class TemplateParser : public LexerHelper std::string m_templateName; const Settings& m_settings; TemplateEnv* m_env = nullptr; - std::basic_regex m_roughTokenizer; - std::basic_regex m_keywords; + BasicRegex m_roughTokenizer; + BasicRegex m_keywords; std::vector m_lines; std::vector m_textBlocks; LineInfo m_currentLineInfo = {}; diff --git a/test/perf_test.cpp b/test/perf_test.cpp index 92afbb4e..d6eefe63 100644 --- a/test/perf_test.cpp +++ b/test/perf_test.cpp @@ -181,6 +181,26 @@ TEST(PerfTests, ForLoopIfText) std::cout << result << std::endl; } +TEST(PerfTests, LoadTemplate) +{ + std::string source = "{{ title }}"; + jinja2::Template tpl; + + const int N = 100000; + + for (int i = 0; i < N; i++) + { + tpl.Load(source); + } + + ValuesMap data; + data["title"] = "My title"; + data["t"] = "My List"; + std::string result = tpl.RenderAsString(data).value();; + + std::cout << result << std::endl; +} + TEST(PerfTests, DISABLED_TestMatsuhiko) { std::string source = R"( diff --git a/thirdparty/CMakeLists.txt b/thirdparty/CMakeLists.txt index a57c5b2a..daba3858 100644 --- a/thirdparty/CMakeLists.txt +++ b/thirdparty/CMakeLists.txt @@ -68,7 +68,7 @@ endif() if (NOT DEFINED JINJA2_PRIVATE_LIBS_INT) set(JINJA2CPP_PRIVATE_LIBS ${JINJA2CPP_PRIVATE_LIBS} Boost::variant - Boost::filesystem Boost::algorithm Boost::lexical_cast Boost::json fmt RapidJson) + Boost::filesystem Boost::algorithm Boost::lexical_cast Boost::json Boost::regex fmt RapidJson) else () set (JINJA2CPP_PRIVATE_LIBS ${JINJA2_PRIVATE_LIBS_INT}) endif () diff --git a/thirdparty/external_boost_deps.cmake b/thirdparty/external_boost_deps.cmake index cd2f7e0b..a14484f2 100644 --- a/thirdparty/external_boost_deps.cmake +++ b/thirdparty/external_boost_deps.cmake @@ -24,19 +24,21 @@ find_package(boost_filesystem ${FIND_BOOST_PACKAGE_QUIET}) find_package(boost_json ${FIND_BOOST_PACKAGE_QUIET}) find_package(boost_optional ${FIND_BOOST_PACKAGE_QUIET}) find_package(boost_variant ${FIND_BOOST_PACKAGE_QUIET}) +find_package(boost_regex ${FIND_BOOST_PACKAGE_QUIET}) if (boost_algorithm_FOUND AND boost_filesystem_FOUND AND boost_json_FOUND AND boost_optional_FOUND AND - boost_variant_FOUND) + boost_variant_FOUND AND boost_regex_FOUND) imported_target_alias(boost_algorithm ALIAS boost_algorithm::boost_algorithm) imported_target_alias(boost_filesystem ALIAS boost_filesystem::boost_filesystem) imported_target_alias(boost_json ALIAS boost_json::boost_json) imported_target_alias(boost_optional ALIAS boost_optional::boost_optional) imported_target_alias(boost_variant ALIAS boost_variant::boost_variant) + imported_target_alias(boost_regex ALIAS boost_regex::boost_regex) else () - find_package(Boost COMPONENTS system filesystem json ${FIND_BOOST_PACKAGE_QUIET} REQUIRED) + find_package(Boost COMPONENTS system filesystem json regex ${FIND_BOOST_PACKAGE_QUIET} REQUIRED) if (Boost_FOUND) imported_target_alias(boost_algorithm ALIAS Boost::boost) @@ -44,10 +46,15 @@ else () imported_target_alias(boost_json ALIAS Boost::json) imported_target_alias(boost_optional ALIAS Boost::boost) imported_target_alias(boost_variant ALIAS Boost::boost) + imported_target_alias(boost_regex ALIAS Boost::regex) endif () endif () -install(TARGETS boost_algorithm boost_filesystem boost_json boost_optional boost_variant +set(_additional_boost_install_targets) +if ("${JINJA2CPP_USE_REGEX}" STREQUAL "boost") +set(_additional_boost_install_targets "boost_regex") +endif() +install(TARGETS boost_algorithm boost_filesystem boost_json boost_optional boost_variant ${_additional_boost_install_targets} EXPORT InstallTargets RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR} LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR} From f90959c7cbfc910c90ffe0051fe34f387824b4e9 Mon Sep 17 00:00:00 2001 From: Ruslan Morozov Date: Fri, 13 Oct 2023 12:26:25 +0300 Subject: [PATCH 11/14] bump 3rd party libs && bump version to 1.3.0 * gtest v1.14.0 * libfmt 10.1.1 * expected-lite latest * rapidjson - latest * boost 1.83 --- CMakeLists.txt | 2 +- conanfile.txt | 13 +++++++++++++ thirdparty/fmtlib | 2 +- thirdparty/gtest | 2 +- thirdparty/json/rapid | 2 +- thirdparty/nonstd/expected-lite | 2 +- thirdparty/thirdparty-conan-build.cmake | 10 +++++----- thirdparty/thirdparty-internal.cmake | 3 ++- 8 files changed, 25 insertions(+), 11 deletions(-) create mode 100644 conanfile.txt diff --git a/CMakeLists.txt b/CMakeLists.txt index 0be30837..971be488 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,5 +1,5 @@ cmake_minimum_required(VERSION 3.23.0) -project(Jinja2Cpp VERSION 1.2.2) +project(Jinja2Cpp VERSION 1.3.0) if (${CMAKE_VERSION} VERSION_GREATER "3.12") cmake_policy(SET CMP0074 OLD) diff --git a/conanfile.txt b/conanfile.txt new file mode 100644 index 00000000..1c661f7e --- /dev/null +++ b/conanfile.txt @@ -0,0 +1,13 @@ +[requires] +boost/1.83.0 +expected-lite/0.6.3 +fmt/10.1.1 +nlohmann_json/3.11.2 +optional-lite/3.5.0 +rapidjson/cci.20220822 +string-view-lite/1.7.0 +variant-lite/2.0.0 + +[generators] +CMakeDeps +CMakeToolchain diff --git a/thirdparty/fmtlib b/thirdparty/fmtlib index a0b8a92e..f5e54359 160000 --- a/thirdparty/fmtlib +++ b/thirdparty/fmtlib @@ -1 +1 @@ -Subproject commit a0b8a92e3d1532361c2f7feb63babc5c18d00ef2 +Subproject commit f5e54359df4c26b6230fc61d38aa294581393084 diff --git a/thirdparty/gtest b/thirdparty/gtest index b796f7d4..f8d7d77c 160000 --- a/thirdparty/gtest +++ b/thirdparty/gtest @@ -1 +1 @@ -Subproject commit b796f7d44681514f58a683a3a71ff17c94edb0c1 +Subproject commit f8d7d77c06936315286eb55f8de22cd23c188571 diff --git a/thirdparty/json/rapid b/thirdparty/json/rapid index 973dc9c0..f9d53419 160000 --- a/thirdparty/json/rapid +++ b/thirdparty/json/rapid @@ -1 +1 @@ -Subproject commit 973dc9c06dcd3d035ebd039cfb9ea457721ec213 +Subproject commit f9d53419e912910fd8fa57d5705fa41425428c35 diff --git a/thirdparty/nonstd/expected-lite b/thirdparty/nonstd/expected-lite index 49af05a2..45a54fac 160000 --- a/thirdparty/nonstd/expected-lite +++ b/thirdparty/nonstd/expected-lite @@ -1 +1 @@ -Subproject commit 49af05a2fdda423f8aa3918c2b96ccfa1857c3dd +Subproject commit 45a54fac224e5aae5f8e70bb1c2423181ae554da diff --git a/thirdparty/thirdparty-conan-build.cmake b/thirdparty/thirdparty-conan-build.cmake index e465b68d..773cb87b 100644 --- a/thirdparty/thirdparty-conan-build.cmake +++ b/thirdparty/thirdparty-conan-build.cmake @@ -6,10 +6,10 @@ find_package(optional-lite REQUIRED) find_package(string-view-lite REQUIRED) find_package(nlohmann_json REQUIRED) -find_package(Boost COMPONENTS algorithm filesystem json optional variant REQUIRED) -set(CONAN_BOOST_PACKAGE_NAME Boost::boost) +find_package(Boost COMPONENTS algorithm filesystem json optional variant regex REQUIRED) find_package(fmt REQUIRED) -find_package(rapidjson REQUIRED) +find_package(RapidJSON REQUIRED) -set(JINJA2_PRIVATE_LIBS_INT ${CONAN_BOOST_PACKAGE_NAME} fmt::fmt rapidjson::rapidjson nlohmann_json::nlohmann_json) -set(JINJA2_PUBLIC_LIBS_INT nonstd::expected-lite nonstd::variant-lite nonstd::optional-lite nonstd::string-view-lite) +set(JINJA2_PRIVATE_LIBS_INT Boost::headers Boost::filesystem) +set(JINJA2_PUBLIC_LIBS_INT Boost::json fmt::fmt rapidjson Boost::regex + nlohmann_json::nlohmann_json nonstd::expected-lite nonstd::variant-lite nonstd::optional-lite nonstd::string-view-lite) diff --git a/thirdparty/thirdparty-internal.cmake b/thirdparty/thirdparty-internal.cmake index 11de37ed..9f17c1fb 100644 --- a/thirdparty/thirdparty-internal.cmake +++ b/thirdparty/thirdparty-internal.cmake @@ -12,13 +12,14 @@ list(APPEND BOOST_INCLUDE_LIBRARIES optional variant json + regex ) include(FetchContent) FetchContent_Declare( Boost GIT_REPOSITORY https://github.com/boostorg/boost.git - GIT_TAG boost-1.82.0 + GIT_TAG boost-1.83.0 PATCH_COMMAND git apply --ignore-whitespace "${CMAKE_CURRENT_LIST_DIR}/../cmake/patches/0001-fix-skip-install-rules.patch" || true ) FetchContent_MakeAvailable(Boost) From 11ebd1e3dec209c9d972e077c967f6ac58e3350b Mon Sep 17 00:00:00 2001 From: Ruslan Morozov Date: Fri, 17 Nov 2023 20:42:03 +0300 Subject: [PATCH 12/14] rework deps population using CMake fetch_content module --- .gitmodules | 24 -------- CMakeLists.txt | 4 -- thirdparty/CMakeLists.txt | 22 ++++--- thirdparty/fmtlib | 1 - thirdparty/gtest | 1 - thirdparty/internal_deps.cmake | 68 ++++++++++++++++------ thirdparty/json/nlohmann | 1 - thirdparty/json/rapid | 1 - thirdparty/nonstd/expected-lite | 1 - thirdparty/nonstd/optional-lite | 1 - thirdparty/nonstd/string-view-lite | 1 - thirdparty/nonstd/variant-lite | 1 - thirdparty/thirdparty-external-boost.cmake | 3 +- thirdparty/thirdparty-external.cmake | 4 +- thirdparty/thirdparty-internal.cmake | 10 ++-- 15 files changed, 72 insertions(+), 71 deletions(-) delete mode 100644 .gitmodules delete mode 160000 thirdparty/fmtlib delete mode 160000 thirdparty/gtest delete mode 160000 thirdparty/json/nlohmann delete mode 160000 thirdparty/json/rapid delete mode 160000 thirdparty/nonstd/expected-lite delete mode 160000 thirdparty/nonstd/optional-lite delete mode 160000 thirdparty/nonstd/string-view-lite delete mode 160000 thirdparty/nonstd/variant-lite diff --git a/.gitmodules b/.gitmodules deleted file mode 100644 index 35685d99..00000000 --- a/.gitmodules +++ /dev/null @@ -1,24 +0,0 @@ -[submodule "thirdparty/gtest"] - path = thirdparty/gtest - url = https://github.com/google/googletest.git -[submodule "thirdparty/nonstd/expected-lite"] - path = thirdparty/nonstd/expected-lite - url = https://github.com/martinmoene/expected-lite.git -[submodule "thirdparty/nonstd/variant-lite"] - path = thirdparty/nonstd/variant-lite - url = https://github.com/martinmoene/variant-lite.git -[submodule "thirdparty/nonstd/optional-lite"] - path = thirdparty/nonstd/optional-lite - url = https://github.com/martinmoene/optional-lite.git -[submodule "thirdparty/nonstd/string-view-lite"] - path = thirdparty/nonstd/string-view-lite - url = https://github.com/martinmoene/string-view-lite.git -[submodule "thirdparty/fmtlib"] - path = thirdparty/fmtlib - url = https://github.com/fmtlib/fmt.git -[submodule "thirdparty/json/nlohmann"] - path = thirdparty/json/nlohmann - url = https://github.com/nlohmann/json.git -[submodule "thirdparty/json/rapid"] - path = thirdparty/json/rapid - url = https://github.com/Tencent/rapidjson.git diff --git a/CMakeLists.txt b/CMakeLists.txt index 971be488..dcbe4b34 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,10 +1,6 @@ cmake_minimum_required(VERSION 3.23.0) project(Jinja2Cpp VERSION 1.3.0) -if (${CMAKE_VERSION} VERSION_GREATER "3.12") - cmake_policy(SET CMP0074 OLD) -endif () - if("${CMAKE_SOURCE_DIR}" STREQUAL "${CMAKE_CURRENT_SOURCE_DIR}") set(JINJA2CPP_IS_MAIN_PROJECT TRUE) else() diff --git a/thirdparty/CMakeLists.txt b/thirdparty/CMakeLists.txt index daba3858..fc8f51de 100644 --- a/thirdparty/CMakeLists.txt +++ b/thirdparty/CMakeLists.txt @@ -46,14 +46,12 @@ endfunction() include (./thirdparty/thirdparty-${JINJA2CPP_DEPS_MODE}.cmake) if(JINJA2CPP_BUILD_TESTS) - find_package(gtest QUIET) + find_package(GTest QUIET) if(gtest_FOUND) imported_target_alias(gtest ALIAS gtest::gtest) else() - message(STATUS "gtest not found, using submodule") - update_submodule(gtest) - + message(STATUS "gtest not found, building own one") if(MSVC) if (THIRDPARTY_RUNTIME_TYPE STREQUAL "/MD" OR THIRDPARTY_RUNTIME_TYPE STREQUAL "/MDd") set (gtest_force_shared_crt ON CACHE BOOL "" FORCE) @@ -61,20 +59,26 @@ if(JINJA2CPP_BUILD_TESTS) set (gtest_force_shared_crt OFF CACHE BOOL "" FORCE) endif () endif () - - add_subdirectory(thirdparty/gtest EXCLUDE_FROM_ALL) + FetchContent_Declare( + googletest + GIT_REPOSITORY https://github.com/google/googletest.git + GIT_TAG main + ) + FetchContent_MakeAvailable(googletest) endif() endif() if (NOT DEFINED JINJA2_PRIVATE_LIBS_INT) - set(JINJA2CPP_PRIVATE_LIBS ${JINJA2CPP_PRIVATE_LIBS} Boost::variant - Boost::filesystem Boost::algorithm Boost::lexical_cast Boost::json Boost::regex fmt RapidJson) + set(JINJA2CPP_PRIVATE_LIBS ${JINJA2CPP_PRIVATE_LIBS} + Boost::variant Boost::filesystem Boost::algorithm Boost::lexical_cast Boost::json + Boost::regex fmt RapidJson) else () set (JINJA2CPP_PRIVATE_LIBS ${JINJA2_PRIVATE_LIBS_INT}) endif () if (NOT DEFINED JINJA2_PUBLIC_LIBS_INT) - set (JINJA2CPP_PUBLIC_LIBS ${JINJA2CPP_PUBLIC_LIBS} expected-lite variant-lite optional-lite string-view-lite) + set (JINJA2CPP_PUBLIC_LIBS ${JINJA2CPP_PUBLIC_LIBS} + expected-lite variant-lite optional-lite string-view-lite) else () set (JINJA2CPP_PUBLIC_LIBS ${JINJA2_PUBLIC_LIBS_INT}) endif () diff --git a/thirdparty/fmtlib b/thirdparty/fmtlib deleted file mode 160000 index f5e54359..00000000 --- a/thirdparty/fmtlib +++ /dev/null @@ -1 +0,0 @@ -Subproject commit f5e54359df4c26b6230fc61d38aa294581393084 diff --git a/thirdparty/gtest b/thirdparty/gtest deleted file mode 160000 index f8d7d77c..00000000 --- a/thirdparty/gtest +++ /dev/null @@ -1 +0,0 @@ -Subproject commit f8d7d77c06936315286eb55f8de22cd23c188571 diff --git a/thirdparty/internal_deps.cmake b/thirdparty/internal_deps.cmake index 43afe2cb..03311db5 100644 --- a/thirdparty/internal_deps.cmake +++ b/thirdparty/internal_deps.cmake @@ -1,40 +1,72 @@ -update_submodule(nonstd/expected-lite) -add_subdirectory(thirdparty/nonstd/expected-lite EXCLUDE_FROM_ALL) +include(FetchContent) -update_submodule(nonstd/variant-lite) -add_subdirectory(thirdparty/nonstd/variant-lite EXCLUDE_FROM_ALL) +FetchContent_Declare( + expected-lite + GIT_REPOSITORY https://github.com/martinmoene/expected-lite.git + GIT_TAG master +) +FetchContent_MakeAvailable(expected-lite) -update_submodule(nonstd/optional-lite) -add_subdirectory(thirdparty/nonstd/optional-lite EXCLUDE_FROM_ALL) +FetchContent_Declare( + variant-lite + GIT_REPOSITORY https://github.com/martinmoene/variant-lite.git + GIT_TAG master +) +FetchContent_MakeAvailable(variant-lite) -update_submodule(nonstd/string-view-lite) -add_subdirectory(thirdparty/nonstd/string-view-lite EXCLUDE_FROM_ALL) +FetchContent_Declare( + optional-lite + GIT_REPOSITORY https://github.com/martinmoene/optional-lite.git + GIT_TAG master +) +FetchContent_MakeAvailable(optional-lite) + +FetchContent_Declare( + string-view-lite + GIT_REPOSITORY https://github.com/martinmoene/string-view-lite.git + GIT_TAG master +) +FetchContent_MakeAvailable(string-view-lite) -update_submodule(fmtlib) set (FMT_INSTALL ON CACHE BOOL "" FORCE) -add_subdirectory(thirdparty/fmtlib EXCLUDE_FROM_ALL) +FetchContent_Declare( + fmt + GIT_REPOSITORY https://github.com/fmtlib/fmt.git + GIT_TAG 10.1.1 +) +FetchContent_MakeAvailable(fmt) -update_submodule(json/rapid) set (RAPIDJSON_BUILD_DOC OFF CACHE BOOL "" FORCE) set (RAPIDJSON_BUILD_EXAMPLES OFF CACHE BOOL "" FORCE) set (RAPIDJSON_BUILD_TESTS OFF CACHE BOOL "" FORCE) set (RAPIDJSON_BUILD_THIRDPARTY_GTEST OFF CACHE BOOL "" FORCE) set (RAPIDJSON_ENABLE_INSTRUMENTATION_OPT OFF CACHE BOOL "" FORCE) -add_subdirectory(thirdparty/json/rapid EXCLUDE_FROM_ALL) -find_package(RapidJSON) + +FetchContent_Declare( + rapidjson + GIT_REPOSITORY https://github.com/Tencent/rapidjson.git + GIT_TAG 973dc9c06dcd3d035ebd039cfb9ea457721ec213 +) +# GIT_TAG f9d53419e912910fd8fa57d5705fa41425428c35 - latest but broken revision +FetchContent_MakeAvailable(rapidjson) +find_package(RapidJSON REQUIRED) add_library(RapidJson INTERFACE) target_include_directories(RapidJson INTERFACE $ $ - ) - +) if (JINJA2CPP_BUILD_TESTS) - update_submodule(json/nlohmann) set (JSON_BuildTests OFF CACHE BOOL "" FORCE) set (JSON_Install OFF CACHE BOOL "" FORCE) set (JSON_MultipleHeaders ON CACHE BOOL "" FORCE) - add_subdirectory(thirdparty/json/nlohmann EXCLUDE_FROM_ALL) + + FetchContent_Declare( + nlohmann_json + GIT_REPOSITORY https://github.com/nlohmann/json.git + GIT_TAG develop + ) + FetchContent_MakeAvailable(nlohmann_json) endif() install (FILES @@ -49,4 +81,4 @@ install (TARGETS RapidJson RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR} LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR} ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR}/static - ) +) diff --git a/thirdparty/json/nlohmann b/thirdparty/json/nlohmann deleted file mode 160000 index bc889afb..00000000 --- a/thirdparty/json/nlohmann +++ /dev/null @@ -1 +0,0 @@ -Subproject commit bc889afb4c5bf1c0d8ee29ef35eaaf4c8bef8a5d diff --git a/thirdparty/json/rapid b/thirdparty/json/rapid deleted file mode 160000 index f9d53419..00000000 --- a/thirdparty/json/rapid +++ /dev/null @@ -1 +0,0 @@ -Subproject commit f9d53419e912910fd8fa57d5705fa41425428c35 diff --git a/thirdparty/nonstd/expected-lite b/thirdparty/nonstd/expected-lite deleted file mode 160000 index 45a54fac..00000000 --- a/thirdparty/nonstd/expected-lite +++ /dev/null @@ -1 +0,0 @@ -Subproject commit 45a54fac224e5aae5f8e70bb1c2423181ae554da diff --git a/thirdparty/nonstd/optional-lite b/thirdparty/nonstd/optional-lite deleted file mode 160000 index 00e9cf5c..00000000 --- a/thirdparty/nonstd/optional-lite +++ /dev/null @@ -1 +0,0 @@ -Subproject commit 00e9cf5ca5a496e857bc6a28ffed9f4189ce6646 diff --git a/thirdparty/nonstd/string-view-lite b/thirdparty/nonstd/string-view-lite deleted file mode 160000 index 5b1d95fe..00000000 --- a/thirdparty/nonstd/string-view-lite +++ /dev/null @@ -1 +0,0 @@ -Subproject commit 5b1d95fe2c0ee18e654876487898b9a423a954db diff --git a/thirdparty/nonstd/variant-lite b/thirdparty/nonstd/variant-lite deleted file mode 160000 index 5015e841..00000000 --- a/thirdparty/nonstd/variant-lite +++ /dev/null @@ -1 +0,0 @@ -Subproject commit 5015e841cf143487f2d7e2f619b618d455658fab diff --git a/thirdparty/thirdparty-external-boost.cmake b/thirdparty/thirdparty-external-boost.cmake index 81a56cbe..20b855db 100644 --- a/thirdparty/thirdparty-external-boost.cmake +++ b/thirdparty/thirdparty-external-boost.cmake @@ -1,4 +1,5 @@ -message(STATUS "'external-boost' dependencies mode selected for Jinja2Cpp. All dependencies are used as submodules except of boost") +message(STATUS "'external-boost' dependencies mode selected for Jinja2Cpp. All +dependencies are built from source pulled from github except of boost") include (./thirdparty/internal_deps.cmake) include (./thirdparty/external_boost_deps.cmake) diff --git a/thirdparty/thirdparty-external.cmake b/thirdparty/thirdparty-external.cmake index 2b7c17b6..524dabec 100644 --- a/thirdparty/thirdparty-external.cmake +++ b/thirdparty/thirdparty-external.cmake @@ -47,7 +47,7 @@ target_include_directories(RapidJson $ $ ) - + if (TARGET fmt-header-only) target_compile_definitions(fmt-header-only INTERFACE FMT_HEADER_ONLY=1) add_library(fmt ALIAS fmt-header-only) @@ -74,5 +74,5 @@ install(TARGETS RapidJson LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR} ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR}/static ) - + include (./thirdparty/external_boost_deps.cmake) diff --git a/thirdparty/thirdparty-internal.cmake b/thirdparty/thirdparty-internal.cmake index 9f17c1fb..eafd93b5 100644 --- a/thirdparty/thirdparty-internal.cmake +++ b/thirdparty/thirdparty-internal.cmake @@ -1,4 +1,4 @@ -message(STATUS "'internal' dependencies mode selected for Jinja2Cpp. All dependencies are used as submodules") +message(STATUS "'internal' dependencies mode selected for Jinja2Cpp. All dependencies will be built from source pulled from github") include (./thirdparty/internal_deps.cmake) @@ -17,10 +17,10 @@ list(APPEND BOOST_INCLUDE_LIBRARIES include(FetchContent) FetchContent_Declare( - Boost - GIT_REPOSITORY https://github.com/boostorg/boost.git - GIT_TAG boost-1.83.0 - PATCH_COMMAND git apply --ignore-whitespace "${CMAKE_CURRENT_LIST_DIR}/../cmake/patches/0001-fix-skip-install-rules.patch" || true + Boost + GIT_REPOSITORY https://github.com/boostorg/boost.git + GIT_TAG boost-1.83.0 + PATCH_COMMAND git apply --ignore-whitespace "${CMAKE_CURRENT_LIST_DIR}/../cmake/patches/0001-fix-skip-install-rules.patch" || true ) FetchContent_MakeAvailable(Boost) From 4eda81a34242ac94c80332a1db854208dcdc5f17 Mon Sep 17 00:00:00 2001 From: Ruslan Morozov Date: Fri, 17 Nov 2023 23:54:51 +0300 Subject: [PATCH 13/14] add boost_json_serializer to replace rapid json one --- include/jinja2cpp/generic_list.h | 2 +- include/jinja2cpp/reflected_value.h | 6 +- include/jinja2cpp/string_helpers.h | 7 +- src/binding/boost_json_serializer.cpp | 209 ++++++++++++++++++++++++++ src/binding/boost_json_serializer.h | 47 ++++++ src/serialize_filters.cpp | 3 + 6 files changed, 267 insertions(+), 7 deletions(-) create mode 100644 src/binding/boost_json_serializer.cpp create mode 100644 src/binding/boost_json_serializer.h diff --git a/include/jinja2cpp/generic_list.h b/include/jinja2cpp/generic_list.h index d18eb406..94360012 100644 --- a/include/jinja2cpp/generic_list.h +++ b/include/jinja2cpp/generic_list.h @@ -6,9 +6,9 @@ #include +#include #include #include -#include namespace jinja2 { diff --git a/include/jinja2cpp/reflected_value.h b/include/jinja2cpp/reflected_value.h index 4947fa16..b6ee12fb 100644 --- a/include/jinja2cpp/reflected_value.h +++ b/include/jinja2cpp/reflected_value.h @@ -5,12 +5,12 @@ #include -#include -#include #include +#include +#include #include #include -#include +#include namespace jinja2 { diff --git a/include/jinja2cpp/string_helpers.h b/include/jinja2cpp/string_helpers.h index 68d77856..e6ac08a4 100644 --- a/include/jinja2cpp/string_helpers.h +++ b/include/jinja2cpp/string_helpers.h @@ -1,12 +1,13 @@ #ifndef JINJA2CPP_STRING_HELPERS_H #define JINJA2CPP_STRING_HELPERS_H -#include #include "value.h" +#include + +#include #include #include -#include namespace jinja2 { @@ -292,4 +293,4 @@ inline std::wstring AsWString(const Value& val) } } // namespace jinja2 -#endif // JINJA2CPP_STRING_HELPERS_H \ No newline at end of file +#endif // JINJA2CPP_STRING_HELPERS_H diff --git a/src/binding/boost_json_serializer.cpp b/src/binding/boost_json_serializer.cpp new file mode 100644 index 00000000..1f6853ab --- /dev/null +++ b/src/binding/boost_json_serializer.cpp @@ -0,0 +1,209 @@ +#include "boost_json_serializer.h" + +#include "../value_visitors.h" + +// #include + +namespace jinja2 +{ +namespace boost_json_serializer +{ +namespace +{ +struct JsonInserter : visitors::BaseVisitor +{ + using BaseVisitor::operator(); + + explicit JsonInserter() {} + + boost::json::value operator()(const ListAdapter& list) const + { + boost::json::array listValue; //(boost::json::kind::array); + + for (auto& v : list) + { + listValue.push_back(Apply(v)); + } + return listValue; + } + + boost::json::value operator()(const MapAdapter& map) const + { + boost::json::object mapNode; //(boost::json::kind::object); + + const auto& keys = map.GetKeys(); + for (auto& k : keys) + { + mapNode.emplace(k.c_str(), Apply(map.GetValueByName(k))); + } + + return mapNode; + } + + boost::json::value operator()(const KeyValuePair& kwPair) const + { + boost::json::object pairNode; //(boost::json::kind::object); + pairNode.emplace(kwPair.key.c_str(), Apply(kwPair.value)); + + return pairNode; + } + + boost::json::value operator()(const std::string& str) const { return boost::json::value(str.c_str()); } + + boost::json::value operator()(const nonstd::string_view& str) const + { + return boost::json::value(boost::json::string(str)); + // str.data(), static_cast(str.size())); + } + + boost::json::value operator()(const std::wstring& str) const + { + auto s = ConvertString(str); + return boost::json::value(s.c_str()); + } + + boost::json::value operator()(const nonstd::wstring_view& str) const + { + auto s = ConvertString(str); + return boost::json::value(s.c_str()); + } + + boost::json::value operator()(bool val) const { return boost::json::value(val); } + + boost::json::value operator()(EmptyValue) const { return boost::json::value(); } + + boost::json::value operator()(const Callable&) const { return boost::json::value(""); } + + boost::json::value operator()(double val) const { return boost::json::value(val); } + + boost::json::value operator()(int64_t val) const { return boost::json::value(val); } + + // boost::json::Document::AllocatorType& m_allocator; +}; +} // namespace + +DocumentWrapper::DocumentWrapper() +// : m_document(std::make_shared()) +{ +} + +ValueWrapper DocumentWrapper::CreateValue(const InternalValue& value) const +{ + auto v = Apply(value); + return ValueWrapper(std::move(v)); +} + +ValueWrapper::ValueWrapper(boost::json::value&& value) + : m_value(std::move(value)) +{ +} + +void PrettyPrint(std::ostream& os, const boost::json::value& jv, uint8_t indent = 4, std::string* indentString = nullptr) +{ + std::string indentString_; + if (!indentString) + indentString = &indentString_; + switch (jv.kind()) + { + case boost::json::kind::object: + { + os << "{\n"; + indentString->append(indent, ' '); + auto const& obj = jv.get_object(); + if (!obj.empty()) + { + auto it = obj.begin(); + for (;;) + { + os << *indentString << boost::json::serialize(it->key()) << " : "; + PrettyPrint(os, it->value(), indent, indentString); + if (++it == obj.end()) + break; + os << ",\n"; + } + } + os << "\n"; + indentString->resize(indentString->size() - indent); + os << *indentString << "}"; + break; + } + + case boost::json::kind::array: + { + //os << "[\n"; + os << "["; + indentString->append(1, ' '); + auto const& arr = jv.get_array(); + if (!arr.empty()) + { + auto it = arr.begin(); + for (;;) + { + os << ((it == arr.begin()) ? "" : *indentString); + PrettyPrint(os, *it, indent, indentString); + if (++it == arr.end()) + break; + //os << ",\n"; + os << ","; + } + } + //os << "\n"; + indentString->resize(indentString->size() - indent); + os << *indentString << "]"; + break; + } + + case boost::json::kind::string: + { + os << boost::json::serialize(jv.get_string()); + break; + } + + case boost::json::kind::uint64: + case boost::json::kind::int64: + case boost::json::kind::double_: + os << jv; + break; + + case boost::json::kind::bool_: + if (jv.get_bool()) + os << "true"; + else + os << "false"; + break; + + case boost::json::kind::null: + os << "null"; + break; + } + + //if (indentString->empty()) + // os << "\n"; +} + +std::string ValueWrapper::AsString(const uint8_t indent) const +{ + // using Writer = boost::json::Writer>; + // using PrettyWriter = boost::json::PrettyWriter>; + std::stringstream ss; + PrettyPrint(ss, m_value, indent); + return ss.str(); + /* boost::json::StringBuffer buffer; */ + /* if (indent == 0) */ + /* { */ + /* Writer writer(buffer); */ + /* m_value.Accept(writer); */ + /* } */ + /* else */ + /* { */ + /* PrettyWriter writer(buffer); */ + /* writer.SetIndent(' ', indent); */ + /* writer.SetFormatOptions(boost::json::kind::FormatSingleLineArray); */ + /* m_value.Accept(writer); */ + /* } */ + + /* return buffer.GetString(); */ +} + +} // namespace boost_json_serializer +} // namespace jinja2 diff --git a/src/binding/boost_json_serializer.h b/src/binding/boost_json_serializer.h new file mode 100644 index 00000000..162333e4 --- /dev/null +++ b/src/binding/boost_json_serializer.h @@ -0,0 +1,47 @@ +#ifndef JINJA2CPP_SRC_BOOST_JSON_SERIALIZER_H +#define JINJA2CPP_SRC_BOOST_JSON_SERIALIZER_H + +#include "../internal_value.h" + +#include + +#include + +namespace jinja2 +{ +namespace boost_json_serializer +{ + +class ValueWrapper +{ + friend class DocumentWrapper; + +public: + ValueWrapper(ValueWrapper&&) = default; + ValueWrapper& operator=(ValueWrapper&&) = default; + + std::string AsString(uint8_t indent = 0) const; + +private: + ValueWrapper(boost::json::value&& value); + + boost::json::value m_value; +}; + +class DocumentWrapper +{ +public: + DocumentWrapper(); + + DocumentWrapper(DocumentWrapper&&) = default; + DocumentWrapper& operator=(DocumentWrapper&&) = default; + + ValueWrapper CreateValue(const InternalValue& value) const; + +private: +}; + +} // namespace boost_json_serializer +} // namespace jinja2 + +#endif // JINJA2CPP_SRC_BOOST_JSON_SERIALIZER_H diff --git a/src/serialize_filters.cpp b/src/serialize_filters.cpp index 95e3fcd1..6ad2c884 100644 --- a/src/serialize_filters.cpp +++ b/src/serialize_filters.cpp @@ -1,4 +1,5 @@ #include "binding/rapid_json_serializer.h" +#include "binding/boost_json_serializer.h" #include "filters.h" #include "generic_adapters.h" #include "out_stream.h" @@ -141,7 +142,9 @@ InternalValue Serialize::Filter(const InternalValue& value, RenderContext& conte if (m_mode == JsonMode) { const auto indent = ConvertToInt(this->GetArgumentValue("indent", context)); + jinja2::rapidjson_serializer::DocumentWrapper jsonDoc; + //jinja2::boost_json_serializer::DocumentWrapper jsonDoc; const auto jsonValue = jsonDoc.CreateValue(value); const auto jsonString = jsonValue.AsString(static_cast(indent)); const auto result = std::accumulate(jsonString.begin(), jsonString.end(), ""s, [](const auto &str, const auto &c) From 17fe19bca753054944b1b5f9ec86744f870e83c8 Mon Sep 17 00:00:00 2001 From: Ruslan Morozov Date: Sun, 19 Nov 2023 20:01:40 +0300 Subject: [PATCH 14/14] * fix c++14 build * change default json binding to boost --- CMakeLists.txt | 8 +- src/binding/boost_json_serializer.cpp | 107 +++++++++++--------------- src/serialize_filters.cpp | 15 ++-- 3 files changed, 63 insertions(+), 67 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index dcbe4b34..175650c8 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -15,7 +15,7 @@ set(JINJA2CPP_SUPPORTED_REGEX std boost) set(JINJA2CPP_USE_REGEX boost CACHE STRING "Use regex parser in lexer, boost works faster on most platforms") set_property(CACHE JINJA2CPP_USE_REGEX PROPERTY STRINGS ${JINJA2CPP_SUPPORTED_REGEX}) set(JINJA2CPP_WITH_JSON_BINDINGS boost nlohmann rapid all none) -set(JINJA2CPP_WITH_JSON_BINDINGS all CACHE STRING "Build with json support") +set(JINJA2CPP_WITH_JSON_BINDINGS boost CACHE STRING "Build with json support(boost|rapid)") set_property(CACHE JINJA2CPP_WITH_JSON_BINDINGS PROPERTY STRINGS ${JINJA2CPP_WITH_JSON_BINDINGS}) set (JINJA2CPP_DEPS_MODE "internal" CACHE STRING "Jinja2Cpp dependency management mode (internal | external | external-boost | conan-build). See documentation for details. 'interal' is default.") option(JINJA2CPP_BUILD_TESTS "Build Jinja2Cpp unit tests" ${JINJA2CPP_IS_MAIN_PROJECT}) @@ -212,11 +212,17 @@ endif () if ("${JINJA2CPP_USE_REGEX}" STREQUAL "boost") set(_regex_define "-DJINJA2CPP_USE_REGEX_BOOST") endif() +if ("${JINJA2CPP_WITH_JSON_BINDINGS}" STREQUAL "boost") + set(_bindings_define "-DJINJA2CPP_WITH_JSON_BINDINGS_BOOST") +elseif("${JINJA2CPP_WITH_JSON_BINDINGS}" STREQUAL "rapid") + set(_bindings_define "-DJINJA2CPP_WITH_JSON_BINDINGS_RAPID") +endif() target_compile_definitions(${LIB_TARGET_NAME} PUBLIC -DBOOST_SYSTEM_NO_DEPRECATED -DBOOST_ERROR_CODE_HEADER_ONLY ${_regex_define} + ${_bindings_define} ) if (JINJA2CPP_BUILD_SHARED) diff --git a/src/binding/boost_json_serializer.cpp b/src/binding/boost_json_serializer.cpp index 1f6853ab..7dbabbbe 100644 --- a/src/binding/boost_json_serializer.cpp +++ b/src/binding/boost_json_serializer.cpp @@ -1,8 +1,10 @@ #include "boost_json_serializer.h" #include "../value_visitors.h" +#include +#include -// #include +template <> struct fmt::formatter : ostream_formatter {}; namespace jinja2 { @@ -52,8 +54,7 @@ struct JsonInserter : visitors::BaseVisitor boost::json::value operator()(const nonstd::string_view& str) const { - return boost::json::value(boost::json::string(str)); - // str.data(), static_cast(str.size())); + return boost::json::value(boost::json::string(str.data(), str.size())); } boost::json::value operator()(const std::wstring& str) const @@ -78,12 +79,10 @@ struct JsonInserter : visitors::BaseVisitor boost::json::value operator()(int64_t val) const { return boost::json::value(val); } - // boost::json::Document::AllocatorType& m_allocator; }; } // namespace DocumentWrapper::DocumentWrapper() -// : m_document(std::make_shared()) { } @@ -98,111 +97,97 @@ ValueWrapper::ValueWrapper(boost::json::value&& value) { } -void PrettyPrint(std::ostream& os, const boost::json::value& jv, uint8_t indent = 4, std::string* indentString = nullptr) +void PrettyPrint(fmt::basic_memory_buffer& os, const boost::json::value& jv, uint8_t indent = 4, int level = 0) { - std::string indentString_; - if (!indentString) - indentString = &indentString_; switch (jv.kind()) { case boost::json::kind::object: { - os << "{\n"; - indentString->append(indent, ' '); - auto const& obj = jv.get_object(); + fmt::format_to(std::back_inserter(os), "{}", '{'); + if (indent != 0) + { + fmt::format_to(std::back_inserter(os), "{}", "\n"); + } + const auto& obj = jv.get_object(); if (!obj.empty()) { auto it = obj.begin(); for (;;) { - os << *indentString << boost::json::serialize(it->key()) << " : "; - PrettyPrint(os, it->value(), indent, indentString); + auto key = boost::json::serialize(it->key()); + fmt::format_to( + std::back_inserter(os), + "{: >{}}{: <{}}", + key, + key.size() + indent * (level + 1), + ":", + (indent == 0) ? 0 : 2 + ); + PrettyPrint(os, it->value(), indent, level + 1); if (++it == obj.end()) break; - os << ",\n"; + fmt::format_to(std::back_inserter(os), "{: <{}}", ",", (indent == 0) ? 0 : 2); } } - os << "\n"; - indentString->resize(indentString->size() - indent); - os << *indentString << "}"; - break; + if (indent != 0) + { + fmt::format_to(std::back_inserter(os), "{}", "\n"); + } + fmt::format_to(std::back_inserter(os), "{: >{}}", "}", (indent * level) + 1); + break; } case boost::json::kind::array: { - //os << "[\n"; - os << "["; - indentString->append(1, ' '); + fmt::format_to(std::back_inserter(os), "["); auto const& arr = jv.get_array(); if (!arr.empty()) { auto it = arr.begin(); for (;;) { - os << ((it == arr.begin()) ? "" : *indentString); - PrettyPrint(os, *it, indent, indentString); + PrettyPrint(os, *it, indent, level + 1); if (++it == arr.end()) break; - //os << ",\n"; - os << ","; + fmt::format_to(std::back_inserter(os), "{: <{}}", ",", (indent == 0) ? 0 : 2); } } - //os << "\n"; - indentString->resize(indentString->size() - indent); - os << *indentString << "]"; + fmt::format_to(std::back_inserter(os), "]"); break; } case boost::json::kind::string: { - os << boost::json::serialize(jv.get_string()); + fmt::format_to(std::back_inserter(os), "{}", boost::json::serialize(jv.get_string())); break; } case boost::json::kind::uint64: case boost::json::kind::int64: case boost::json::kind::double_: - os << jv; - break; - + { + fmt::format_to(std::back_inserter(os), "{}", jv); + break; + } case boost::json::kind::bool_: - if (jv.get_bool()) - os << "true"; - else - os << "false"; + { + fmt::format_to(std::back_inserter(os), "{}", jv.get_bool()); break; + } case boost::json::kind::null: - os << "null"; + { + fmt::format_to(std::back_inserter(os), "null"); break; } - - //if (indentString->empty()) - // os << "\n"; + } } std::string ValueWrapper::AsString(const uint8_t indent) const { - // using Writer = boost::json::Writer>; - // using PrettyWriter = boost::json::PrettyWriter>; - std::stringstream ss; - PrettyPrint(ss, m_value, indent); - return ss.str(); - /* boost::json::StringBuffer buffer; */ - /* if (indent == 0) */ - /* { */ - /* Writer writer(buffer); */ - /* m_value.Accept(writer); */ - /* } */ - /* else */ - /* { */ - /* PrettyWriter writer(buffer); */ - /* writer.SetIndent(' ', indent); */ - /* writer.SetFormatOptions(boost::json::kind::FormatSingleLineArray); */ - /* m_value.Accept(writer); */ - /* } */ - - /* return buffer.GetString(); */ + fmt::memory_buffer out; + PrettyPrint(out, m_value, indent); + return fmt::to_string(out); } } // namespace boost_json_serializer diff --git a/src/serialize_filters.cpp b/src/serialize_filters.cpp index 6ad2c884..e467fd8c 100644 --- a/src/serialize_filters.cpp +++ b/src/serialize_filters.cpp @@ -1,5 +1,3 @@ -#include "binding/rapid_json_serializer.h" -#include "binding/boost_json_serializer.h" #include "filters.h" #include "generic_adapters.h" #include "out_stream.h" @@ -15,6 +13,15 @@ #include #include +#ifdef JINJA2CPP_WITH_JSON_BINDINGS_BOOST +#include "binding/boost_json_serializer.h" +using DocumentWrapper = jinja2::boost_json_serializer::DocumentWrapper; +#else +#include "binding/rapid_json_serializer.h" +using DocumentWrapper = jinja2::rapidjson_serializer::DocumentWrapper; +#endif + + using namespace std::string_literals; namespace jinja2 @@ -142,9 +149,7 @@ InternalValue Serialize::Filter(const InternalValue& value, RenderContext& conte if (m_mode == JsonMode) { const auto indent = ConvertToInt(this->GetArgumentValue("indent", context)); - - jinja2::rapidjson_serializer::DocumentWrapper jsonDoc; - //jinja2::boost_json_serializer::DocumentWrapper jsonDoc; + DocumentWrapper jsonDoc; const auto jsonValue = jsonDoc.CreateValue(value); const auto jsonString = jsonValue.AsString(static_cast(indent)); const auto result = std::accumulate(jsonString.begin(), jsonString.end(), ""s, [](const auto &str, const auto &c)