aboutsummaryrefslogtreecommitdiffstats
path: root/contrib/libs/jinja2cpp
diff options
context:
space:
mode:
authorMaxim Yurchuk <maxim-yurchuk@ydb.tech>2024-10-18 20:31:38 +0300
committerGitHub <noreply@github.com>2024-10-18 20:31:38 +0300
commit2a74bac2d2d3bccb4e10120f1ead805640ec9dd0 (patch)
tree047e4818ced5aaf73f58517629e5260b5291f9f0 /contrib/libs/jinja2cpp
parent2d9656823e9521d8c29ea4c9a1d0eab78391abfc (diff)
parent3d834a1923bbf9403cd4a448e7f32b670aa4124f (diff)
downloadydb-2a74bac2d2d3bccb4e10120f1ead805640ec9dd0.tar.gz
Merge pull request #10502 from ydb-platform/mergelibs-241016-1210
Library import 241016-1210
Diffstat (limited to 'contrib/libs/jinja2cpp')
-rw-r--r--contrib/libs/jinja2cpp/.yandex_meta/devtools.copyrights.report52
-rw-r--r--contrib/libs/jinja2cpp/.yandex_meta/devtools.licenses.report125
-rw-r--r--contrib/libs/jinja2cpp/.yandex_meta/licenses.list.txt818
-rw-r--r--contrib/libs/jinja2cpp/LICENSE373
-rw-r--r--contrib/libs/jinja2cpp/README.md384
-rw-r--r--contrib/libs/jinja2cpp/include/jinja2cpp/binding/rapid_json.h201
-rw-r--r--contrib/libs/jinja2cpp/include/jinja2cpp/config.h27
-rw-r--r--contrib/libs/jinja2cpp/include/jinja2cpp/error_info.h185
-rw-r--r--contrib/libs/jinja2cpp/include/jinja2cpp/filesystem_handler.h201
-rw-r--r--contrib/libs/jinja2cpp/include/jinja2cpp/generic_list.h287
-rw-r--r--contrib/libs/jinja2cpp/include/jinja2cpp/generic_list_iterator.h104
-rw-r--r--contrib/libs/jinja2cpp/include/jinja2cpp/polymorphic_value.h447
-rw-r--r--contrib/libs/jinja2cpp/include/jinja2cpp/reflected_value.h601
-rw-r--r--contrib/libs/jinja2cpp/include/jinja2cpp/string_helpers.h296
-rw-r--r--contrib/libs/jinja2cpp/include/jinja2cpp/template.h305
-rw-r--r--contrib/libs/jinja2cpp/include/jinja2cpp/template_env.h320
-rw-r--r--contrib/libs/jinja2cpp/include/jinja2cpp/utils/i_comparable.h16
-rw-r--r--contrib/libs/jinja2cpp/include/jinja2cpp/value.h763
-rw-r--r--contrib/libs/jinja2cpp/include/jinja2cpp/value_ptr.h29
-rw-r--r--contrib/libs/jinja2cpp/src/ast_visitor.h117
-rw-r--r--contrib/libs/jinja2cpp/src/binding/rapid_json_serializer.cpp127
-rw-r--r--contrib/libs/jinja2cpp/src/binding/rapid_json_serializer.h50
-rw-r--r--contrib/libs/jinja2cpp/src/error_handling.h63
-rw-r--r--contrib/libs/jinja2cpp/src/error_info.cpp289
-rw-r--r--contrib/libs/jinja2cpp/src/expression_evaluator.cpp602
-rw-r--r--contrib/libs/jinja2cpp/src/expression_evaluator.h619
-rw-r--r--contrib/libs/jinja2cpp/src/expression_parser.cpp606
-rw-r--r--contrib/libs/jinja2cpp/src/expression_parser.h49
-rw-r--r--contrib/libs/jinja2cpp/src/filesystem_handler.cpp163
-rw-r--r--contrib/libs/jinja2cpp/src/filters.cpp1086
-rw-r--r--contrib/libs/jinja2cpp/src/filters.h474
-rw-r--r--contrib/libs/jinja2cpp/src/function_base.h49
-rw-r--r--contrib/libs/jinja2cpp/src/generic_adapters.h215
-rw-r--r--contrib/libs/jinja2cpp/src/generic_list.cpp38
-rw-r--r--contrib/libs/jinja2cpp/src/helpers.h119
-rw-r--r--contrib/libs/jinja2cpp/src/internal_value.cpp1053
-rw-r--r--contrib/libs/jinja2cpp/src/internal_value.h673
-rw-r--r--contrib/libs/jinja2cpp/src/lexer.cpp122
-rw-r--r--contrib/libs/jinja2cpp/src/lexer.h375
-rw-r--r--contrib/libs/jinja2cpp/src/lexertk.h1842
-rw-r--r--contrib/libs/jinja2cpp/src/out_stream.h43
-rw-r--r--contrib/libs/jinja2cpp/src/render_context.h182
-rw-r--r--contrib/libs/jinja2cpp/src/renderer.h131
-rw-r--r--contrib/libs/jinja2cpp/src/robin_hood.h2544
-rw-r--r--contrib/libs/jinja2cpp/src/serialize_filters.cpp442
-rw-r--r--contrib/libs/jinja2cpp/src/statements.cpp839
-rw-r--r--contrib/libs/jinja2cpp/src/statements.h661
-rw-r--r--contrib/libs/jinja2cpp/src/string_converter_filter.cpp395
-rw-r--r--contrib/libs/jinja2cpp/src/template.cpp190
-rw-r--r--contrib/libs/jinja2cpp/src/template_env.cpp95
-rw-r--r--contrib/libs/jinja2cpp/src/template_impl.h487
-rw-r--r--contrib/libs/jinja2cpp/src/template_parser.cpp972
-rw-r--r--contrib/libs/jinja2cpp/src/template_parser.h1081
-rw-r--r--contrib/libs/jinja2cpp/src/testers.cpp379
-rw-r--r--contrib/libs/jinja2cpp/src/testers.h116
-rw-r--r--contrib/libs/jinja2cpp/src/value.cpp143
-rw-r--r--contrib/libs/jinja2cpp/src/value_helpers.h167
-rw-r--r--contrib/libs/jinja2cpp/src/value_visitors.h1151
-rw-r--r--contrib/libs/jinja2cpp/ya.make68
59 files changed, 24351 insertions, 0 deletions
diff --git a/contrib/libs/jinja2cpp/.yandex_meta/devtools.copyrights.report b/contrib/libs/jinja2cpp/.yandex_meta/devtools.copyrights.report
new file mode 100644
index 0000000000..f1ac2b9dae
--- /dev/null
+++ b/contrib/libs/jinja2cpp/.yandex_meta/devtools.copyrights.report
@@ -0,0 +1,52 @@
+# File format ($ symbol means the beginning of a line):
+#
+# $ # this message
+# $ # =======================
+# $ # comments (all commentaries should starts with some number of spaces and # symbol)
+# ${action} {license id} {license text hash}
+# $BELONGS ./ya/make/file/relative/path/1/ya.make ./ya/make/2/ya.make
+# ${all_file_action} filename
+# $ # user commentaries (many lines)
+# $ generated description - files with this license, license text... (some number of lines that starts with some number of spaces, do not modify)
+# ${action} {license spdx} {license text hash}
+# $BELONGS ./ya/make/file/relative/path/3/ya.make
+# ${all_file_action} filename
+# $ # user commentaries
+# $ generated description
+# $ ...
+#
+# You can modify action, all_file_action and add commentaries
+# Available actions:
+# keep - keep license in contrib and use in credits
+# skip - skip license
+# remove - remove all files with this license
+# rename - save license text/links into licenses texts file, but not store SPDX into LINCENSE macro. You should store correct license id into devtools.license.spdx.txt file
+#
+# {all file action} records will be generated when license text contains filename that exists on filesystem (in contrib directory)
+# We suppose that that files can contain some license info
+# Available all file actions:
+# FILE_IGNORE - ignore file (do nothing)
+# FILE_INCLUDE - include all file data into licenses text file
+# =======================
+
+KEEP COPYRIGHT_SERVICE_LABEL 097cfbf1153a4f7ed38e64fd7e2e3993
+BELONGS ya.make
+ License text:
+ Copyright (c) 2016 Jonathan B. Coe
+ Scancode info:
+ Original SPDX id: COPYRIGHT_SERVICE_LABEL
+ Score : 100.00
+ Match type : COPYRIGHT
+ Files with this license:
+ include/jinja2cpp/polymorphic_value.h [3:3]
+
+KEEP COPYRIGHT_SERVICE_LABEL b64d8f4e77f6cb007626437192f93d68
+BELONGS ya.make
+ License text:
+ // Copyright (c) 2018-2021 Martin Ankerl <http://martin.ankerl.com>
+ Scancode info:
+ Original SPDX id: COPYRIGHT_SERVICE_LABEL
+ Score : 100.00
+ Match type : COPYRIGHT
+ Files with this license:
+ src/robin_hood.h [13:13]
diff --git a/contrib/libs/jinja2cpp/.yandex_meta/devtools.licenses.report b/contrib/libs/jinja2cpp/.yandex_meta/devtools.licenses.report
new file mode 100644
index 0000000000..c548c28d7b
--- /dev/null
+++ b/contrib/libs/jinja2cpp/.yandex_meta/devtools.licenses.report
@@ -0,0 +1,125 @@
+# File format ($ symbol means the beginning of a line):
+#
+# $ # this message
+# $ # =======================
+# $ # comments (all commentaries should starts with some number of spaces and # symbol)
+# ${action} {license spdx} {license text hash}
+# $BELONGS ./ya/make/file/relative/path/1/ya.make ./ya/make/2/ya.make
+# ${all_file_action} filename
+# $ # user commentaries (many lines)
+# $ generated description - files with this license, license text... (some number of lines that starts with some number of spaces, do not modify)
+# ${action} {license spdx} {license text hash}
+# $BELONGS ./ya/make/file/relative/path/3/ya.make
+# ${all_file_action} filename
+# $ # user commentaries
+# $ generated description
+# $ ...
+#
+# You can modify action, all_file_action and add commentaries
+# Available actions:
+# keep - keep license in contrib and use in credits
+# skip - skip license
+# remove - remove all files with this license
+# rename - save license text/links into licenses texts file, but not store SPDX into LINCENSE macro. You should store correct license id into devtools.license.spdx.txt file
+#
+# {all file action} records will be generated when license text contains filename that exists on filesystem (in contrib directory)
+# We suppose that that files can contain some license info
+# Available all file actions:
+# FILE_IGNORE - ignore file (do nothing)
+# FILE_INCLUDE - include all file data into licenses text file
+# =======================
+
+KEEP MIT 4dfb1f9f7c68ff4156bccff8f3de1b60
+BELONGS ya.make
+ License text:
+ [![GitHub License](https://img.shields.io/badge/license-Mozilla-blue.svg)](https://raw.githubusercontent.com/jinja2cpp/Jinja2Cpp/master/LICENSE)
+ Scancode info:
+ Original SPDX id: MIT
+ Score : 85.71
+ Match type : TAG
+ Links : http://opensource.org/licenses/mit-license.php, https://spdx.org/licenses/MIT
+ Files with this license:
+ README.md [12:12]
+
+KEEP MIT 6b45d159c11839114dfda208e6140eda
+BELONGS ya.make
+ Note: matched license text is too long. Read it in the source files.
+ Scancode info:
+ Original SPDX id: MIT
+ Score : 100.00
+ Match type : TEXT
+ Links : http://opensource.org/licenses/mit-license.php, https://spdx.org/licenses/MIT
+ Files with this license:
+ src/robin_hood.h [15:31]
+
+KEEP MPL-2.0 81bec2692a2a04ebc459d6dca672caf4
+BELONGS ya.make
+FILE_INCLUDE LICENSE found in files: LICENSE at line 363
+ Note: matched license text is too long. Read it in the source files.
+ Scancode info:
+ Original SPDX id: MPL-2.0
+ Score : 100.00
+ Match type : TEXT
+ Links : http://mpl.mozilla.org/2012/01/03/announcing-mpl-2-0/, http://www.mozilla.com/MPL/2.0/, https://spdx.org/licenses/MPL-2.0
+ Files with this license:
+ LICENSE [1:373]
+
+KEEP MIT 8384d75c38c570f3edb87cf9f64f2ec2
+BELONGS ya.make
+ License text:
+ // SPDX-License-Identifier: MIT
+ Scancode info:
+ Original SPDX id: MIT
+ Score : 100.00
+ Match type : TAG
+ Links : http://opensource.org/licenses/mit-license.php, https://spdx.org/licenses/MIT
+ Files with this license:
+ src/robin_hood.h [12:12]
+
+KEEP MIT a6e9f2d79eb73e6e422759b53da6152a
+BELONGS ya.make
+ Note: matched license text is too long. Read it in the source files.
+ Scancode info:
+ Original SPDX id: MIT
+ Score : 100.00
+ Match type : TEXT
+ Links : http://opensource.org/licenses/mit-license.php, https://spdx.org/licenses/MIT
+ Files with this license:
+ include/jinja2cpp/polymorphic_value.h [5:20]
+
+KEEP MIT b6657f1432f3f3405667101097117109
+BELONGS ya.make
+ License text:
+ // Licensed under the MIT License <http://opensource.org/licenses/MIT>.
+ Scancode info:
+ Original SPDX id: MIT
+ Score : 100.00
+ Match type : NOTICE
+ Links : http://opensource.org/licenses/mit-license.php, https://spdx.org/licenses/MIT
+ Files with this license:
+ src/robin_hood.h [11:11]
+
+KEEP MPL-1.1 bbcc868f743e472bc5bc01c0a6b359a5
+BELONGS ya.make
+ License text:
+ [![GitHub License](https://img.shields.io/badge/license-Mozilla-blue.svg)](https://raw.githubusercontent.com/jinja2cpp/Jinja2Cpp/master/LICENSE)
+ Scancode info:
+ Original SPDX id: MPL-1.1
+ Score : 90.00
+ Match type : TAG
+ Links : http://www.mozilla.com/MPL/1.1/index.html, http://www.mozilla.org/MPL/MPL-1.1.html, https://spdx.org/licenses/MPL-1.1
+ Files with this license:
+ README.md [12:12]
+
+KEEP MIT e7fc849ec7d035354afc7a26365b817d
+BELONGS ya.make
+ License text:
+ * under the guidelines and in accordance with the MIT License. *
+ * http://www.opensource.org/licenses/MIT *
+ Scancode info:
+ Original SPDX id: MIT
+ Score : 81.82
+ Match type : REFERENCE
+ Links : http://opensource.org/licenses/mit-license.php, https://spdx.org/licenses/MIT
+ Files with this license:
+ src/lexertk.h [11:12]
diff --git a/contrib/libs/jinja2cpp/.yandex_meta/licenses.list.txt b/contrib/libs/jinja2cpp/.yandex_meta/licenses.list.txt
new file mode 100644
index 0000000000..c054605553
--- /dev/null
+++ b/contrib/libs/jinja2cpp/.yandex_meta/licenses.list.txt
@@ -0,0 +1,818 @@
+====================COPYRIGHT====================
+// Copyright (c) 2018-2021 Martin Ankerl <http://martin.ankerl.com>
+
+
+====================COPYRIGHT====================
+Copyright (c) 2016 Jonathan B. Coe
+
+
+====================File: LICENSE====================
+Mozilla Public License Version 2.0
+==================================
+
+1. Definitions
+--------------
+
+1.1. "Contributor"
+ means each individual or legal entity that creates, contributes to
+ the creation of, or owns Covered Software.
+
+1.2. "Contributor Version"
+ means the combination of the Contributions of others (if any) used
+ by a Contributor and that particular Contributor's Contribution.
+
+1.3. "Contribution"
+ means Covered Software of a particular Contributor.
+
+1.4. "Covered Software"
+ means Source Code Form to which the initial Contributor has attached
+ the notice in Exhibit A, the Executable Form of such Source Code
+ Form, and Modifications of such Source Code Form, in each case
+ including portions thereof.
+
+1.5. "Incompatible With Secondary Licenses"
+ means
+
+ (a) that the initial Contributor has attached the notice described
+ in Exhibit B to the Covered Software; or
+
+ (b) that the Covered Software was made available under the terms of
+ version 1.1 or earlier of the License, but not also under the
+ terms of a Secondary License.
+
+1.6. "Executable Form"
+ means any form of the work other than Source Code Form.
+
+1.7. "Larger Work"
+ means a work that combines Covered Software with other material, in
+ a separate file or files, that is not Covered Software.
+
+1.8. "License"
+ means this document.
+
+1.9. "Licensable"
+ means having the right to grant, to the maximum extent possible,
+ whether at the time of the initial grant or subsequently, any and
+ all of the rights conveyed by this License.
+
+1.10. "Modifications"
+ means any of the following:
+
+ (a) any file in Source Code Form that results from an addition to,
+ deletion from, or modification of the contents of Covered
+ Software; or
+
+ (b) any new file in Source Code Form that contains any Covered
+ Software.
+
+1.11. "Patent Claims" of a Contributor
+ means any patent claim(s), including without limitation, method,
+ process, and apparatus claims, in any patent Licensable by such
+ Contributor that would be infringed, but for the grant of the
+ License, by the making, using, selling, offering for sale, having
+ made, import, or transfer of either its Contributions or its
+ Contributor Version.
+
+1.12. "Secondary License"
+ means either the GNU General Public License, Version 2.0, the GNU
+ Lesser General Public License, Version 2.1, the GNU Affero General
+ Public License, Version 3.0, or any later versions of those
+ licenses.
+
+1.13. "Source Code Form"
+ means the form of the work preferred for making modifications.
+
+1.14. "You" (or "Your")
+ means an individual or a legal entity exercising rights under this
+ License. For legal entities, "You" includes any entity that
+ controls, is controlled by, or is under common control with You. For
+ purposes of this definition, "control" means (a) the power, direct
+ or indirect, to cause the direction or management of such entity,
+ whether by contract or otherwise, or (b) ownership of more than
+ fifty percent (50%) of the outstanding shares or beneficial
+ ownership of such entity.
+
+2. License Grants and Conditions
+--------------------------------
+
+2.1. Grants
+
+Each Contributor hereby grants You a world-wide, royalty-free,
+non-exclusive license:
+
+(a) under intellectual property rights (other than patent or trademark)
+ Licensable by such Contributor to use, reproduce, make available,
+ modify, display, perform, distribute, and otherwise exploit its
+ Contributions, either on an unmodified basis, with Modifications, or
+ as part of a Larger Work; and
+
+(b) under Patent Claims of such Contributor to make, use, sell, offer
+ for sale, have made, import, and otherwise transfer either its
+ Contributions or its Contributor Version.
+
+2.2. Effective Date
+
+The licenses granted in Section 2.1 with respect to any Contribution
+become effective for each Contribution on the date the Contributor first
+distributes such Contribution.
+
+2.3. Limitations on Grant Scope
+
+The licenses granted in this Section 2 are the only rights granted under
+this License. No additional rights or licenses will be implied from the
+distribution or licensing of Covered Software under this License.
+Notwithstanding Section 2.1(b) above, no patent license is granted by a
+Contributor:
+
+(a) for any code that a Contributor has removed from Covered Software;
+ or
+
+(b) for infringements caused by: (i) Your and any other third party's
+ modifications of Covered Software, or (ii) the combination of its
+ Contributions with other software (except as part of its Contributor
+ Version); or
+
+(c) under Patent Claims infringed by Covered Software in the absence of
+ its Contributions.
+
+This License does not grant any rights in the trademarks, service marks,
+or logos of any Contributor (except as may be necessary to comply with
+the notice requirements in Section 3.4).
+
+2.4. Subsequent Licenses
+
+No Contributor makes additional grants as a result of Your choice to
+distribute the Covered Software under a subsequent version of this
+License (see Section 10.2) or under the terms of a Secondary License (if
+permitted under the terms of Section 3.3).
+
+2.5. Representation
+
+Each Contributor represents that the Contributor believes its
+Contributions are its original creation(s) or it has sufficient rights
+to grant the rights to its Contributions conveyed by this License.
+
+2.6. Fair Use
+
+This License is not intended to limit any rights You have under
+applicable copyright doctrines of fair use, fair dealing, or other
+equivalents.
+
+2.7. Conditions
+
+Sections 3.1, 3.2, 3.3, and 3.4 are conditions of the licenses granted
+in Section 2.1.
+
+3. Responsibilities
+-------------------
+
+3.1. Distribution of Source Form
+
+All distribution of Covered Software in Source Code Form, including any
+Modifications that You create or to which You contribute, must be under
+the terms of this License. You must inform recipients that the Source
+Code Form of the Covered Software is governed by the terms of this
+License, and how they can obtain a copy of this License. You may not
+attempt to alter or restrict the recipients' rights in the Source Code
+Form.
+
+3.2. Distribution of Executable Form
+
+If You distribute Covered Software in Executable Form then:
+
+(a) such Covered Software must also be made available in Source Code
+ Form, as described in Section 3.1, and You must inform recipients of
+ the Executable Form how they can obtain a copy of such Source Code
+ Form by reasonable means in a timely manner, at a charge no more
+ than the cost of distribution to the recipient; and
+
+(b) You may distribute such Executable Form under the terms of this
+ License, or sublicense it under different terms, provided that the
+ license for the Executable Form does not attempt to limit or alter
+ the recipients' rights in the Source Code Form under this License.
+
+3.3. Distribution of a Larger Work
+
+You may create and distribute a Larger Work under terms of Your choice,
+provided that You also comply with the requirements of this License for
+the Covered Software. If the Larger Work is a combination of Covered
+Software with a work governed by one or more Secondary Licenses, and the
+Covered Software is not Incompatible With Secondary Licenses, this
+License permits You to additionally distribute such Covered Software
+under the terms of such Secondary License(s), so that the recipient of
+the Larger Work may, at their option, further distribute the Covered
+Software under the terms of either this License or such Secondary
+License(s).
+
+3.4. Notices
+
+You may not remove or alter the substance of any license notices
+(including copyright notices, patent notices, disclaimers of warranty,
+or limitations of liability) contained within the Source Code Form of
+the Covered Software, except that You may alter any license notices to
+the extent required to remedy known factual inaccuracies.
+
+3.5. Application of Additional Terms
+
+You may choose to offer, and to charge a fee for, warranty, support,
+indemnity or liability obligations to one or more recipients of Covered
+Software. However, You may do so only on Your own behalf, and not on
+behalf of any Contributor. You must make it absolutely clear that any
+such warranty, support, indemnity, or liability obligation is offered by
+You alone, and You hereby agree to indemnify every Contributor for any
+liability incurred by such Contributor as a result of warranty, support,
+indemnity or liability terms You offer. You may include additional
+disclaimers of warranty and limitations of liability specific to any
+jurisdiction.
+
+4. Inability to Comply Due to Statute or Regulation
+---------------------------------------------------
+
+If it is impossible for You to comply with any of the terms of this
+License with respect to some or all of the Covered Software due to
+statute, judicial order, or regulation then You must: (a) comply with
+the terms of this License to the maximum extent possible; and (b)
+describe the limitations and the code they affect. Such description must
+be placed in a text file included with all distributions of the Covered
+Software under this License. Except to the extent prohibited by statute
+or regulation, such description must be sufficiently detailed for a
+recipient of ordinary skill to be able to understand it.
+
+5. Termination
+--------------
+
+5.1. The rights granted under this License will terminate automatically
+if You fail to comply with any of its terms. However, if You become
+compliant, then the rights granted under this License from a particular
+Contributor are reinstated (a) provisionally, unless and until such
+Contributor explicitly and finally terminates Your grants, and (b) on an
+ongoing basis, if such Contributor fails to notify You of the
+non-compliance by some reasonable means prior to 60 days after You have
+come back into compliance. Moreover, Your grants from a particular
+Contributor are reinstated on an ongoing basis if such Contributor
+notifies You of the non-compliance by some reasonable means, this is the
+first time You have received notice of non-compliance with this License
+from such Contributor, and You become compliant prior to 30 days after
+Your receipt of the notice.
+
+5.2. If You initiate litigation against any entity by asserting a patent
+infringement claim (excluding declaratory judgment actions,
+counter-claims, and cross-claims) alleging that a Contributor Version
+directly or indirectly infringes any patent, then the rights granted to
+You by any and all Contributors for the Covered Software under Section
+2.1 of this License shall terminate.
+
+5.3. In the event of termination under Sections 5.1 or 5.2 above, all
+end user license agreements (excluding distributors and resellers) which
+have been validly granted by You or Your distributors under this License
+prior to termination shall survive termination.
+
+************************************************************************
+* *
+* 6. Disclaimer of Warranty *
+* ------------------------- *
+* *
+* Covered Software is provided under this License on an "as is" *
+* basis, without warranty of any kind, either expressed, implied, or *
+* statutory, including, without limitation, warranties that the *
+* Covered Software is free of defects, merchantable, fit for a *
+* particular purpose or non-infringing. The entire risk as to the *
+* quality and performance of the Covered Software is with You. *
+* Should any Covered Software prove defective in any respect, You *
+* (not any Contributor) assume the cost of any necessary servicing, *
+* repair, or correction. This disclaimer of warranty constitutes an *
+* essential part of this License. No use of any Covered Software is *
+* authorized under this License except under this disclaimer. *
+* *
+************************************************************************
+
+************************************************************************
+* *
+* 7. Limitation of Liability *
+* -------------------------- *
+* *
+* Under no circumstances and under no legal theory, whether tort *
+* (including negligence), contract, or otherwise, shall any *
+* Contributor, or anyone who distributes Covered Software as *
+* permitted above, be liable to You for any direct, indirect, *
+* special, incidental, or consequential damages of any character *
+* including, without limitation, damages for lost profits, loss of *
+* goodwill, work stoppage, computer failure or malfunction, or any *
+* and all other commercial damages or losses, even if such party *
+* shall have been informed of the possibility of such damages. This *
+* limitation of liability shall not apply to liability for death or *
+* personal injury resulting from such party's negligence to the *
+* extent applicable law prohibits such limitation. Some *
+* jurisdictions do not allow the exclusion or limitation of *
+* incidental or consequential damages, so this exclusion and *
+* limitation may not apply to You. *
+* *
+************************************************************************
+
+8. Litigation
+-------------
+
+Any litigation relating to this License may be brought only in the
+courts of a jurisdiction where the defendant maintains its principal
+place of business and such litigation shall be governed by laws of that
+jurisdiction, without reference to its conflict-of-law provisions.
+Nothing in this Section shall prevent a party's ability to bring
+cross-claims or counter-claims.
+
+9. Miscellaneous
+----------------
+
+This License represents the complete agreement concerning the subject
+matter hereof. If any provision of this License is held to be
+unenforceable, such provision shall be reformed only to the extent
+necessary to make it enforceable. Any law or regulation which provides
+that the language of a contract shall be construed against the drafter
+shall not be used to construe this License against a Contributor.
+
+10. Versions of the License
+---------------------------
+
+10.1. New Versions
+
+Mozilla Foundation is the license steward. Except as provided in Section
+10.3, no one other than the license steward has the right to modify or
+publish new versions of this License. Each version will be given a
+distinguishing version number.
+
+10.2. Effect of New Versions
+
+You may distribute the Covered Software under the terms of the version
+of the License under which You originally received the Covered Software,
+or under the terms of any subsequent version published by the license
+steward.
+
+10.3. Modified Versions
+
+If you create software not governed by this License, and you want to
+create a new license for such software, you may create and use a
+modified version of this License if you rename the license and remove
+any references to the name of the license steward (except to note that
+such modified license differs from this License).
+
+10.4. Distributing Source Code Form that is Incompatible With Secondary
+Licenses
+
+If You choose to distribute Source Code Form that is Incompatible With
+Secondary Licenses under the terms of this version of the License, the
+notice described in Exhibit B of this License must be attached.
+
+Exhibit A - Source Code Form License Notice
+-------------------------------------------
+
+ This Source Code Form is subject to the terms of the Mozilla Public
+ License, v. 2.0. If a copy of the MPL was not distributed with this
+ file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+If it is not possible or desirable to put the notice in a particular
+file, then You may include the notice in a location (such as a LICENSE
+file in a relevant directory) where a recipient would be likely to look
+for such a notice.
+
+You may add additional accurate notices of copyright ownership.
+
+Exhibit B - "Incompatible With Secondary Licenses" Notice
+---------------------------------------------------------
+
+ This Source Code Form is "Incompatible With Secondary Licenses", as
+ defined by the Mozilla Public License, v. 2.0.
+
+
+====================MIT====================
+ * under the guidelines and in accordance with the MIT License. *
+ * http://www.opensource.org/licenses/MIT *
+
+
+====================MIT====================
+// Licensed under the MIT License <http://opensource.org/licenses/MIT>.
+
+
+====================MIT====================
+// 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.
+
+
+====================MIT====================
+// SPDX-License-Identifier: MIT
+
+
+====================MIT====================
+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.
+
+
+====================MIT====================
+[![GitHub License](https://img.shields.io/badge/license-Mozilla-blue.svg)](https://raw.githubusercontent.com/jinja2cpp/Jinja2Cpp/master/LICENSE)
+
+
+====================MPL-1.1====================
+[![GitHub License](https://img.shields.io/badge/license-Mozilla-blue.svg)](https://raw.githubusercontent.com/jinja2cpp/Jinja2Cpp/master/LICENSE)
+
+
+====================MPL-2.0====================
+Mozilla Public License Version 2.0
+==================================
+
+1. Definitions
+--------------
+
+1.1. "Contributor"
+ means each individual or legal entity that creates, contributes to
+ the creation of, or owns Covered Software.
+
+1.2. "Contributor Version"
+ means the combination of the Contributions of others (if any) used
+ by a Contributor and that particular Contributor's Contribution.
+
+1.3. "Contribution"
+ means Covered Software of a particular Contributor.
+
+1.4. "Covered Software"
+ means Source Code Form to which the initial Contributor has attached
+ the notice in Exhibit A, the Executable Form of such Source Code
+ Form, and Modifications of such Source Code Form, in each case
+ including portions thereof.
+
+1.5. "Incompatible With Secondary Licenses"
+ means
+
+ (a) that the initial Contributor has attached the notice described
+ in Exhibit B to the Covered Software; or
+
+ (b) that the Covered Software was made available under the terms of
+ version 1.1 or earlier of the License, but not also under the
+ terms of a Secondary License.
+
+1.6. "Executable Form"
+ means any form of the work other than Source Code Form.
+
+1.7. "Larger Work"
+ means a work that combines Covered Software with other material, in
+ a separate file or files, that is not Covered Software.
+
+1.8. "License"
+ means this document.
+
+1.9. "Licensable"
+ means having the right to grant, to the maximum extent possible,
+ whether at the time of the initial grant or subsequently, any and
+ all of the rights conveyed by this License.
+
+1.10. "Modifications"
+ means any of the following:
+
+ (a) any file in Source Code Form that results from an addition to,
+ deletion from, or modification of the contents of Covered
+ Software; or
+
+ (b) any new file in Source Code Form that contains any Covered
+ Software.
+
+1.11. "Patent Claims" of a Contributor
+ means any patent claim(s), including without limitation, method,
+ process, and apparatus claims, in any patent Licensable by such
+ Contributor that would be infringed, but for the grant of the
+ License, by the making, using, selling, offering for sale, having
+ made, import, or transfer of either its Contributions or its
+ Contributor Version.
+
+1.12. "Secondary License"
+ means either the GNU General Public License, Version 2.0, the GNU
+ Lesser General Public License, Version 2.1, the GNU Affero General
+ Public License, Version 3.0, or any later versions of those
+ licenses.
+
+1.13. "Source Code Form"
+ means the form of the work preferred for making modifications.
+
+1.14. "You" (or "Your")
+ means an individual or a legal entity exercising rights under this
+ License. For legal entities, "You" includes any entity that
+ controls, is controlled by, or is under common control with You. For
+ purposes of this definition, "control" means (a) the power, direct
+ or indirect, to cause the direction or management of such entity,
+ whether by contract or otherwise, or (b) ownership of more than
+ fifty percent (50%) of the outstanding shares or beneficial
+ ownership of such entity.
+
+2. License Grants and Conditions
+--------------------------------
+
+2.1. Grants
+
+Each Contributor hereby grants You a world-wide, royalty-free,
+non-exclusive license:
+
+(a) under intellectual property rights (other than patent or trademark)
+ Licensable by such Contributor to use, reproduce, make available,
+ modify, display, perform, distribute, and otherwise exploit its
+ Contributions, either on an unmodified basis, with Modifications, or
+ as part of a Larger Work; and
+
+(b) under Patent Claims of such Contributor to make, use, sell, offer
+ for sale, have made, import, and otherwise transfer either its
+ Contributions or its Contributor Version.
+
+2.2. Effective Date
+
+The licenses granted in Section 2.1 with respect to any Contribution
+become effective for each Contribution on the date the Contributor first
+distributes such Contribution.
+
+2.3. Limitations on Grant Scope
+
+The licenses granted in this Section 2 are the only rights granted under
+this License. No additional rights or licenses will be implied from the
+distribution or licensing of Covered Software under this License.
+Notwithstanding Section 2.1(b) above, no patent license is granted by a
+Contributor:
+
+(a) for any code that a Contributor has removed from Covered Software;
+ or
+
+(b) for infringements caused by: (i) Your and any other third party's
+ modifications of Covered Software, or (ii) the combination of its
+ Contributions with other software (except as part of its Contributor
+ Version); or
+
+(c) under Patent Claims infringed by Covered Software in the absence of
+ its Contributions.
+
+This License does not grant any rights in the trademarks, service marks,
+or logos of any Contributor (except as may be necessary to comply with
+the notice requirements in Section 3.4).
+
+2.4. Subsequent Licenses
+
+No Contributor makes additional grants as a result of Your choice to
+distribute the Covered Software under a subsequent version of this
+License (see Section 10.2) or under the terms of a Secondary License (if
+permitted under the terms of Section 3.3).
+
+2.5. Representation
+
+Each Contributor represents that the Contributor believes its
+Contributions are its original creation(s) or it has sufficient rights
+to grant the rights to its Contributions conveyed by this License.
+
+2.6. Fair Use
+
+This License is not intended to limit any rights You have under
+applicable copyright doctrines of fair use, fair dealing, or other
+equivalents.
+
+2.7. Conditions
+
+Sections 3.1, 3.2, 3.3, and 3.4 are conditions of the licenses granted
+in Section 2.1.
+
+3. Responsibilities
+-------------------
+
+3.1. Distribution of Source Form
+
+All distribution of Covered Software in Source Code Form, including any
+Modifications that You create or to which You contribute, must be under
+the terms of this License. You must inform recipients that the Source
+Code Form of the Covered Software is governed by the terms of this
+License, and how they can obtain a copy of this License. You may not
+attempt to alter or restrict the recipients' rights in the Source Code
+Form.
+
+3.2. Distribution of Executable Form
+
+If You distribute Covered Software in Executable Form then:
+
+(a) such Covered Software must also be made available in Source Code
+ Form, as described in Section 3.1, and You must inform recipients of
+ the Executable Form how they can obtain a copy of such Source Code
+ Form by reasonable means in a timely manner, at a charge no more
+ than the cost of distribution to the recipient; and
+
+(b) You may distribute such Executable Form under the terms of this
+ License, or sublicense it under different terms, provided that the
+ license for the Executable Form does not attempt to limit or alter
+ the recipients' rights in the Source Code Form under this License.
+
+3.3. Distribution of a Larger Work
+
+You may create and distribute a Larger Work under terms of Your choice,
+provided that You also comply with the requirements of this License for
+the Covered Software. If the Larger Work is a combination of Covered
+Software with a work governed by one or more Secondary Licenses, and the
+Covered Software is not Incompatible With Secondary Licenses, this
+License permits You to additionally distribute such Covered Software
+under the terms of such Secondary License(s), so that the recipient of
+the Larger Work may, at their option, further distribute the Covered
+Software under the terms of either this License or such Secondary
+License(s).
+
+3.4. Notices
+
+You may not remove or alter the substance of any license notices
+(including copyright notices, patent notices, disclaimers of warranty,
+or limitations of liability) contained within the Source Code Form of
+the Covered Software, except that You may alter any license notices to
+the extent required to remedy known factual inaccuracies.
+
+3.5. Application of Additional Terms
+
+You may choose to offer, and to charge a fee for, warranty, support,
+indemnity or liability obligations to one or more recipients of Covered
+Software. However, You may do so only on Your own behalf, and not on
+behalf of any Contributor. You must make it absolutely clear that any
+such warranty, support, indemnity, or liability obligation is offered by
+You alone, and You hereby agree to indemnify every Contributor for any
+liability incurred by such Contributor as a result of warranty, support,
+indemnity or liability terms You offer. You may include additional
+disclaimers of warranty and limitations of liability specific to any
+jurisdiction.
+
+4. Inability to Comply Due to Statute or Regulation
+---------------------------------------------------
+
+If it is impossible for You to comply with any of the terms of this
+License with respect to some or all of the Covered Software due to
+statute, judicial order, or regulation then You must: (a) comply with
+the terms of this License to the maximum extent possible; and (b)
+describe the limitations and the code they affect. Such description must
+be placed in a text file included with all distributions of the Covered
+Software under this License. Except to the extent prohibited by statute
+or regulation, such description must be sufficiently detailed for a
+recipient of ordinary skill to be able to understand it.
+
+5. Termination
+--------------
+
+5.1. The rights granted under this License will terminate automatically
+if You fail to comply with any of its terms. However, if You become
+compliant, then the rights granted under this License from a particular
+Contributor are reinstated (a) provisionally, unless and until such
+Contributor explicitly and finally terminates Your grants, and (b) on an
+ongoing basis, if such Contributor fails to notify You of the
+non-compliance by some reasonable means prior to 60 days after You have
+come back into compliance. Moreover, Your grants from a particular
+Contributor are reinstated on an ongoing basis if such Contributor
+notifies You of the non-compliance by some reasonable means, this is the
+first time You have received notice of non-compliance with this License
+from such Contributor, and You become compliant prior to 30 days after
+Your receipt of the notice.
+
+5.2. If You initiate litigation against any entity by asserting a patent
+infringement claim (excluding declaratory judgment actions,
+counter-claims, and cross-claims) alleging that a Contributor Version
+directly or indirectly infringes any patent, then the rights granted to
+You by any and all Contributors for the Covered Software under Section
+2.1 of this License shall terminate.
+
+5.3. In the event of termination under Sections 5.1 or 5.2 above, all
+end user license agreements (excluding distributors and resellers) which
+have been validly granted by You or Your distributors under this License
+prior to termination shall survive termination.
+
+************************************************************************
+* *
+* 6. Disclaimer of Warranty *
+* ------------------------- *
+* *
+* Covered Software is provided under this License on an "as is" *
+* basis, without warranty of any kind, either expressed, implied, or *
+* statutory, including, without limitation, warranties that the *
+* Covered Software is free of defects, merchantable, fit for a *
+* particular purpose or non-infringing. The entire risk as to the *
+* quality and performance of the Covered Software is with You. *
+* Should any Covered Software prove defective in any respect, You *
+* (not any Contributor) assume the cost of any necessary servicing, *
+* repair, or correction. This disclaimer of warranty constitutes an *
+* essential part of this License. No use of any Covered Software is *
+* authorized under this License except under this disclaimer. *
+* *
+************************************************************************
+
+************************************************************************
+* *
+* 7. Limitation of Liability *
+* -------------------------- *
+* *
+* Under no circumstances and under no legal theory, whether tort *
+* (including negligence), contract, or otherwise, shall any *
+* Contributor, or anyone who distributes Covered Software as *
+* permitted above, be liable to You for any direct, indirect, *
+* special, incidental, or consequential damages of any character *
+* including, without limitation, damages for lost profits, loss of *
+* goodwill, work stoppage, computer failure or malfunction, or any *
+* and all other commercial damages or losses, even if such party *
+* shall have been informed of the possibility of such damages. This *
+* limitation of liability shall not apply to liability for death or *
+* personal injury resulting from such party's negligence to the *
+* extent applicable law prohibits such limitation. Some *
+* jurisdictions do not allow the exclusion or limitation of *
+* incidental or consequential damages, so this exclusion and *
+* limitation may not apply to You. *
+* *
+************************************************************************
+
+8. Litigation
+-------------
+
+Any litigation relating to this License may be brought only in the
+courts of a jurisdiction where the defendant maintains its principal
+place of business and such litigation shall be governed by laws of that
+jurisdiction, without reference to its conflict-of-law provisions.
+Nothing in this Section shall prevent a party's ability to bring
+cross-claims or counter-claims.
+
+9. Miscellaneous
+----------------
+
+This License represents the complete agreement concerning the subject
+matter hereof. If any provision of this License is held to be
+unenforceable, such provision shall be reformed only to the extent
+necessary to make it enforceable. Any law or regulation which provides
+that the language of a contract shall be construed against the drafter
+shall not be used to construe this License against a Contributor.
+
+10. Versions of the License
+---------------------------
+
+10.1. New Versions
+
+Mozilla Foundation is the license steward. Except as provided in Section
+10.3, no one other than the license steward has the right to modify or
+publish new versions of this License. Each version will be given a
+distinguishing version number.
+
+10.2. Effect of New Versions
+
+You may distribute the Covered Software under the terms of the version
+of the License under which You originally received the Covered Software,
+or under the terms of any subsequent version published by the license
+steward.
+
+10.3. Modified Versions
+
+If you create software not governed by this License, and you want to
+create a new license for such software, you may create and use a
+modified version of this License if you rename the license and remove
+any references to the name of the license steward (except to note that
+such modified license differs from this License).
+
+10.4. Distributing Source Code Form that is Incompatible With Secondary
+Licenses
+
+If You choose to distribute Source Code Form that is Incompatible With
+Secondary Licenses under the terms of this version of the License, the
+notice described in Exhibit B of this License must be attached.
+
+Exhibit A - Source Code Form License Notice
+-------------------------------------------
+
+ This Source Code Form is subject to the terms of the Mozilla Public
+ License, v. 2.0. If a copy of the MPL was not distributed with this
+ file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+If it is not possible or desirable to put the notice in a particular
+file, then You may include the notice in a location (such as a LICENSE
+file in a relevant directory) where a recipient would be likely to look
+for such a notice.
+
+You may add additional accurate notices of copyright ownership.
+
+Exhibit B - "Incompatible With Secondary Licenses" Notice
+---------------------------------------------------------
+
+ This Source Code Form is "Incompatible With Secondary Licenses", as
+ defined by the Mozilla Public License, v. 2.0. \ No newline at end of file
diff --git a/contrib/libs/jinja2cpp/LICENSE b/contrib/libs/jinja2cpp/LICENSE
new file mode 100644
index 0000000000..a612ad9813
--- /dev/null
+++ b/contrib/libs/jinja2cpp/LICENSE
@@ -0,0 +1,373 @@
+Mozilla Public License Version 2.0
+==================================
+
+1. Definitions
+--------------
+
+1.1. "Contributor"
+ means each individual or legal entity that creates, contributes to
+ the creation of, or owns Covered Software.
+
+1.2. "Contributor Version"
+ means the combination of the Contributions of others (if any) used
+ by a Contributor and that particular Contributor's Contribution.
+
+1.3. "Contribution"
+ means Covered Software of a particular Contributor.
+
+1.4. "Covered Software"
+ means Source Code Form to which the initial Contributor has attached
+ the notice in Exhibit A, the Executable Form of such Source Code
+ Form, and Modifications of such Source Code Form, in each case
+ including portions thereof.
+
+1.5. "Incompatible With Secondary Licenses"
+ means
+
+ (a) that the initial Contributor has attached the notice described
+ in Exhibit B to the Covered Software; or
+
+ (b) that the Covered Software was made available under the terms of
+ version 1.1 or earlier of the License, but not also under the
+ terms of a Secondary License.
+
+1.6. "Executable Form"
+ means any form of the work other than Source Code Form.
+
+1.7. "Larger Work"
+ means a work that combines Covered Software with other material, in
+ a separate file or files, that is not Covered Software.
+
+1.8. "License"
+ means this document.
+
+1.9. "Licensable"
+ means having the right to grant, to the maximum extent possible,
+ whether at the time of the initial grant or subsequently, any and
+ all of the rights conveyed by this License.
+
+1.10. "Modifications"
+ means any of the following:
+
+ (a) any file in Source Code Form that results from an addition to,
+ deletion from, or modification of the contents of Covered
+ Software; or
+
+ (b) any new file in Source Code Form that contains any Covered
+ Software.
+
+1.11. "Patent Claims" of a Contributor
+ means any patent claim(s), including without limitation, method,
+ process, and apparatus claims, in any patent Licensable by such
+ Contributor that would be infringed, but for the grant of the
+ License, by the making, using, selling, offering for sale, having
+ made, import, or transfer of either its Contributions or its
+ Contributor Version.
+
+1.12. "Secondary License"
+ means either the GNU General Public License, Version 2.0, the GNU
+ Lesser General Public License, Version 2.1, the GNU Affero General
+ Public License, Version 3.0, or any later versions of those
+ licenses.
+
+1.13. "Source Code Form"
+ means the form of the work preferred for making modifications.
+
+1.14. "You" (or "Your")
+ means an individual or a legal entity exercising rights under this
+ License. For legal entities, "You" includes any entity that
+ controls, is controlled by, or is under common control with You. For
+ purposes of this definition, "control" means (a) the power, direct
+ or indirect, to cause the direction or management of such entity,
+ whether by contract or otherwise, or (b) ownership of more than
+ fifty percent (50%) of the outstanding shares or beneficial
+ ownership of such entity.
+
+2. License Grants and Conditions
+--------------------------------
+
+2.1. Grants
+
+Each Contributor hereby grants You a world-wide, royalty-free,
+non-exclusive license:
+
+(a) under intellectual property rights (other than patent or trademark)
+ Licensable by such Contributor to use, reproduce, make available,
+ modify, display, perform, distribute, and otherwise exploit its
+ Contributions, either on an unmodified basis, with Modifications, or
+ as part of a Larger Work; and
+
+(b) under Patent Claims of such Contributor to make, use, sell, offer
+ for sale, have made, import, and otherwise transfer either its
+ Contributions or its Contributor Version.
+
+2.2. Effective Date
+
+The licenses granted in Section 2.1 with respect to any Contribution
+become effective for each Contribution on the date the Contributor first
+distributes such Contribution.
+
+2.3. Limitations on Grant Scope
+
+The licenses granted in this Section 2 are the only rights granted under
+this License. No additional rights or licenses will be implied from the
+distribution or licensing of Covered Software under this License.
+Notwithstanding Section 2.1(b) above, no patent license is granted by a
+Contributor:
+
+(a) for any code that a Contributor has removed from Covered Software;
+ or
+
+(b) for infringements caused by: (i) Your and any other third party's
+ modifications of Covered Software, or (ii) the combination of its
+ Contributions with other software (except as part of its Contributor
+ Version); or
+
+(c) under Patent Claims infringed by Covered Software in the absence of
+ its Contributions.
+
+This License does not grant any rights in the trademarks, service marks,
+or logos of any Contributor (except as may be necessary to comply with
+the notice requirements in Section 3.4).
+
+2.4. Subsequent Licenses
+
+No Contributor makes additional grants as a result of Your choice to
+distribute the Covered Software under a subsequent version of this
+License (see Section 10.2) or under the terms of a Secondary License (if
+permitted under the terms of Section 3.3).
+
+2.5. Representation
+
+Each Contributor represents that the Contributor believes its
+Contributions are its original creation(s) or it has sufficient rights
+to grant the rights to its Contributions conveyed by this License.
+
+2.6. Fair Use
+
+This License is not intended to limit any rights You have under
+applicable copyright doctrines of fair use, fair dealing, or other
+equivalents.
+
+2.7. Conditions
+
+Sections 3.1, 3.2, 3.3, and 3.4 are conditions of the licenses granted
+in Section 2.1.
+
+3. Responsibilities
+-------------------
+
+3.1. Distribution of Source Form
+
+All distribution of Covered Software in Source Code Form, including any
+Modifications that You create or to which You contribute, must be under
+the terms of this License. You must inform recipients that the Source
+Code Form of the Covered Software is governed by the terms of this
+License, and how they can obtain a copy of this License. You may not
+attempt to alter or restrict the recipients' rights in the Source Code
+Form.
+
+3.2. Distribution of Executable Form
+
+If You distribute Covered Software in Executable Form then:
+
+(a) such Covered Software must also be made available in Source Code
+ Form, as described in Section 3.1, and You must inform recipients of
+ the Executable Form how they can obtain a copy of such Source Code
+ Form by reasonable means in a timely manner, at a charge no more
+ than the cost of distribution to the recipient; and
+
+(b) You may distribute such Executable Form under the terms of this
+ License, or sublicense it under different terms, provided that the
+ license for the Executable Form does not attempt to limit or alter
+ the recipients' rights in the Source Code Form under this License.
+
+3.3. Distribution of a Larger Work
+
+You may create and distribute a Larger Work under terms of Your choice,
+provided that You also comply with the requirements of this License for
+the Covered Software. If the Larger Work is a combination of Covered
+Software with a work governed by one or more Secondary Licenses, and the
+Covered Software is not Incompatible With Secondary Licenses, this
+License permits You to additionally distribute such Covered Software
+under the terms of such Secondary License(s), so that the recipient of
+the Larger Work may, at their option, further distribute the Covered
+Software under the terms of either this License or such Secondary
+License(s).
+
+3.4. Notices
+
+You may not remove or alter the substance of any license notices
+(including copyright notices, patent notices, disclaimers of warranty,
+or limitations of liability) contained within the Source Code Form of
+the Covered Software, except that You may alter any license notices to
+the extent required to remedy known factual inaccuracies.
+
+3.5. Application of Additional Terms
+
+You may choose to offer, and to charge a fee for, warranty, support,
+indemnity or liability obligations to one or more recipients of Covered
+Software. However, You may do so only on Your own behalf, and not on
+behalf of any Contributor. You must make it absolutely clear that any
+such warranty, support, indemnity, or liability obligation is offered by
+You alone, and You hereby agree to indemnify every Contributor for any
+liability incurred by such Contributor as a result of warranty, support,
+indemnity or liability terms You offer. You may include additional
+disclaimers of warranty and limitations of liability specific to any
+jurisdiction.
+
+4. Inability to Comply Due to Statute or Regulation
+---------------------------------------------------
+
+If it is impossible for You to comply with any of the terms of this
+License with respect to some or all of the Covered Software due to
+statute, judicial order, or regulation then You must: (a) comply with
+the terms of this License to the maximum extent possible; and (b)
+describe the limitations and the code they affect. Such description must
+be placed in a text file included with all distributions of the Covered
+Software under this License. Except to the extent prohibited by statute
+or regulation, such description must be sufficiently detailed for a
+recipient of ordinary skill to be able to understand it.
+
+5. Termination
+--------------
+
+5.1. The rights granted under this License will terminate automatically
+if You fail to comply with any of its terms. However, if You become
+compliant, then the rights granted under this License from a particular
+Contributor are reinstated (a) provisionally, unless and until such
+Contributor explicitly and finally terminates Your grants, and (b) on an
+ongoing basis, if such Contributor fails to notify You of the
+non-compliance by some reasonable means prior to 60 days after You have
+come back into compliance. Moreover, Your grants from a particular
+Contributor are reinstated on an ongoing basis if such Contributor
+notifies You of the non-compliance by some reasonable means, this is the
+first time You have received notice of non-compliance with this License
+from such Contributor, and You become compliant prior to 30 days after
+Your receipt of the notice.
+
+5.2. If You initiate litigation against any entity by asserting a patent
+infringement claim (excluding declaratory judgment actions,
+counter-claims, and cross-claims) alleging that a Contributor Version
+directly or indirectly infringes any patent, then the rights granted to
+You by any and all Contributors for the Covered Software under Section
+2.1 of this License shall terminate.
+
+5.3. In the event of termination under Sections 5.1 or 5.2 above, all
+end user license agreements (excluding distributors and resellers) which
+have been validly granted by You or Your distributors under this License
+prior to termination shall survive termination.
+
+************************************************************************
+* *
+* 6. Disclaimer of Warranty *
+* ------------------------- *
+* *
+* Covered Software is provided under this License on an "as is" *
+* basis, without warranty of any kind, either expressed, implied, or *
+* statutory, including, without limitation, warranties that the *
+* Covered Software is free of defects, merchantable, fit for a *
+* particular purpose or non-infringing. The entire risk as to the *
+* quality and performance of the Covered Software is with You. *
+* Should any Covered Software prove defective in any respect, You *
+* (not any Contributor) assume the cost of any necessary servicing, *
+* repair, or correction. This disclaimer of warranty constitutes an *
+* essential part of this License. No use of any Covered Software is *
+* authorized under this License except under this disclaimer. *
+* *
+************************************************************************
+
+************************************************************************
+* *
+* 7. Limitation of Liability *
+* -------------------------- *
+* *
+* Under no circumstances and under no legal theory, whether tort *
+* (including negligence), contract, or otherwise, shall any *
+* Contributor, or anyone who distributes Covered Software as *
+* permitted above, be liable to You for any direct, indirect, *
+* special, incidental, or consequential damages of any character *
+* including, without limitation, damages for lost profits, loss of *
+* goodwill, work stoppage, computer failure or malfunction, or any *
+* and all other commercial damages or losses, even if such party *
+* shall have been informed of the possibility of such damages. This *
+* limitation of liability shall not apply to liability for death or *
+* personal injury resulting from such party's negligence to the *
+* extent applicable law prohibits such limitation. Some *
+* jurisdictions do not allow the exclusion or limitation of *
+* incidental or consequential damages, so this exclusion and *
+* limitation may not apply to You. *
+* *
+************************************************************************
+
+8. Litigation
+-------------
+
+Any litigation relating to this License may be brought only in the
+courts of a jurisdiction where the defendant maintains its principal
+place of business and such litigation shall be governed by laws of that
+jurisdiction, without reference to its conflict-of-law provisions.
+Nothing in this Section shall prevent a party's ability to bring
+cross-claims or counter-claims.
+
+9. Miscellaneous
+----------------
+
+This License represents the complete agreement concerning the subject
+matter hereof. If any provision of this License is held to be
+unenforceable, such provision shall be reformed only to the extent
+necessary to make it enforceable. Any law or regulation which provides
+that the language of a contract shall be construed against the drafter
+shall not be used to construe this License against a Contributor.
+
+10. Versions of the License
+---------------------------
+
+10.1. New Versions
+
+Mozilla Foundation is the license steward. Except as provided in Section
+10.3, no one other than the license steward has the right to modify or
+publish new versions of this License. Each version will be given a
+distinguishing version number.
+
+10.2. Effect of New Versions
+
+You may distribute the Covered Software under the terms of the version
+of the License under which You originally received the Covered Software,
+or under the terms of any subsequent version published by the license
+steward.
+
+10.3. Modified Versions
+
+If you create software not governed by this License, and you want to
+create a new license for such software, you may create and use a
+modified version of this License if you rename the license and remove
+any references to the name of the license steward (except to note that
+such modified license differs from this License).
+
+10.4. Distributing Source Code Form that is Incompatible With Secondary
+Licenses
+
+If You choose to distribute Source Code Form that is Incompatible With
+Secondary Licenses under the terms of this version of the License, the
+notice described in Exhibit B of this License must be attached.
+
+Exhibit A - Source Code Form License Notice
+-------------------------------------------
+
+ This Source Code Form is subject to the terms of the Mozilla Public
+ License, v. 2.0. If a copy of the MPL was not distributed with this
+ file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+If it is not possible or desirable to put the notice in a particular
+file, then You may include the notice in a location (such as a LICENSE
+file in a relevant directory) where a recipient would be likely to look
+for such a notice.
+
+You may add additional accurate notices of copyright ownership.
+
+Exhibit B - "Incompatible With Secondary Licenses" Notice
+---------------------------------------------------------
+
+ This Source Code Form is "Incompatible With Secondary Licenses", as
+ defined by the Mozilla Public License, v. 2.0.
diff --git a/contrib/libs/jinja2cpp/README.md b/contrib/libs/jinja2cpp/README.md
new file mode 100644
index 0000000000..2fc58e46ac
--- /dev/null
+++ b/contrib/libs/jinja2cpp/README.md
@@ -0,0 +1,384 @@
+<div align="center"><img width="200" src="https://avatars0.githubusercontent.com/u/49841676?s=200&v=4"></div>
+
+# Jinja2С++
+
+[![Language](https://img.shields.io/badge/language-C++-blue.svg)](https://isocpp.org/)
+[![Standard](https://img.shields.io/badge/c%2B%2B-14-blue.svg)](https://en.wikipedia.org/wiki/C%2B%2B#Standardization)
+[![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)
+[![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.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.
+
+## Introduction
+
+Main features of Jinja2C++:
+- Easy-to-use public interface. Just load templates and render them.
+- Conformance to [Jinja2 specification](http://jinja.pocoo.org/docs/2.10/)
+- Full support of narrow- and wide-character strings both for templates and parameters.
+- Built-in reflection for the common C++ types, nlohmann and rapid JSON libraries.
+- Powerful full-featured Jinja2 expressions with filtering (via '|' operator) and 'if'-expressions.
+- Control statements (`set`, `for`, `if`, `filter`, `do`, `with`).
+- Templates extension, including and importing
+- Macros
+- Rich error reporting.
+- Shared template environment with templates cache support
+
+For instance, this simple code:
+
+```c++
+#include <jinja2cpp/template.h>
+
+std::string source = R"(
+{{ ("Hello", 'world') | join }}!!!
+{{ ("Hello", 'world') | join(', ') }}!!!
+{{ ("Hello", 'world') | join(d = '; ') }}!!!
+{{ ("Hello", 'world') | join(d = '; ') | lower }}!!!
+)";
+
+Template tpl;
+tpl.Load(source);
+
+std::string result = tpl.RenderAsString({}).value();
+```
+
+produces the result string:
+
+```
+Helloworld!!!
+Hello, world!!!
+Hello; world!!!
+hello; world!!!
+```
+
+## Getting started
+
+To use Jinja2C++ in your project you have to:
+* Clone the Jinja2C++ repository
+* 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:
+1. Declare the jinja2::Template object:
+
+```c++
+jinja2::Template tpl;
+```
+
+2. Populate it with template:
+
+```c++
+tpl.Load("{{ 'Hello World' }}!!!");
+```
+
+3. Render the template:
+
+```c++
+std::cout << tpl.RenderAsString({}).value() << std::endl;
+```
+
+and get:
+
+`
+Hello World!!!
+`
+
+That's all!
+
+More detailed examples and features description can be found in the documentation: [https://jinja2cpp.github.io/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:
+- expressions. You can use almost every expression style: simple, filtered, conditional, and so on.
+- the big number of filters (**sort, default, first, last, length, max, min, reverse, unique, sum, attr, map, reject, rejectattr, select, selectattr, pprint, dictsort, abs, float, int, list, round, random, trim, title, upper, wordcount, replace, truncate, groupby, urlencode, capitalize, escape, tojson, striptags, center, xmlattr**)
+- the big number of testers (**eq, defined, ge, gt, iterable, le, lt, mapping, ne, number, sequence, string, undefined, in, even, odd, lower, upper**)
+- the number of functions (**range**, **loop.cycle**)
+- 'if' statement (with 'elif' and 'else' branches)
+- 'for' statement (with 'else' branch and 'if' part support)
+- 'include' statement
+- 'import'/'from' statements
+- 'set' statement (both line and block)
+- 'filter' statement
+- 'extends'/'block' statements
+- 'macro'/'call' statements
+- 'with' statement
+- 'do' extension statement
+- recursive loops
+- space control and 'raw'/'endraw' blocks
+
+Full information about Jinja2 specification support and compatibility table can be found here: [https://jinja2cpp.github.io/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):
+- Linux gcc 5.5 - 9.0
+- Linux clang 5.0 - 9
+- MacOS X-Code 9
+- MacOS X-Code 10
+- MacOS X-Code 11 (C++14 in default build, C++17 with externally-provided boost)
+- Microsoft Visual Studio 2015 - 2019 x86, x64
+- MinGW gcc compiler 7.3
+- MinGW gcc compiler 8.1
+
+**Note:** Support of gcc version >= 9.x or clang version >= 8.0 depends on the version of the Boost library provided.
+
+### Build status
+
+| Compiler | Status |
+|---------|---------:|
+| **MSVC** 2015 (x86, x64), **MinGW** 7 (x64), **MinGW** 8 (x64) | [![Build status](https://ci.appveyor.com/api/projects/status/vu59lw4r67n8jdxl/branch/master?svg=true)](https://ci.appveyor.com/project/flexferrum/jinja2cpp-n5hjm/branch/master) |
+| **X-Code** 9, 10, 11 | [![Build Status](https://travis-ci.org/jinja2cpp/Jinja2Cpp.svg?branch=master)](https://travis-ci.org/jinja2cpp/Jinja2Cpp) |
+| **MSVC** 2017 (x86, x64), **MSVC** 2019 (x86, x64), C++14/C++17 | [![](https://github.com/jinja2cpp/Jinja2Cpp/workflows/CI-windows-build/badge.svg)](https://github.com/jinja2cpp/Jinja2Cpp/actions?query=workflow%3ACI-windows-build) |
+| **g++** 5, 6, 7, 8, 9, 10, 11 **clang** 5, 6, 7, 8, 9, 10, 11, 12 C++14/C++17/C++20 | [![](https://github.com/jinja2cpp/Jinja2Cpp/workflows/CI-linux-build/badge.svg)](https://github.com/jinja2cpp/Jinja2Cpp/actions?query=workflow%3ACI-linux-build) |
+
+## Build and install
+Jinja2C++ has several external dependencies:
+- `boost` library (at least version 1.65)
+- `nonstd::expected-lite` [https://github.com/martinmoene/expected-lite](https://github.com/martinmoene/expected-lite)
+- `nonstd::variant-lite` [https://github.com/martinmoene/variant-lite](https://github.com/martinmoene/variant-lite)
+- `nonstd::optional-lite` [https://github.com/martinmoene/optional-lite](https://github.com/martinmoene/optional-lite)
+- `nonstd::string-view-lite` [https://github.com/martinmoene/string-view-lite](https://github.com/martinmoene/string-view-lite)
+- `fmtlib::fmt` [https://github.com/fmtlib/fmt](https://github.com/fmtlib/fmt)
+
+Examples of build scripts and different build configurations could be found here: [https://github.com/jinja2cpp/examples-build](https://github.com/jinja2cpp/examples-build)
+
+In simplest case to compile Jinja2C++ you need:
+
+1. Install CMake build system (at least version 3.0)
+2. Clone jinja2cpp repository:
+
+```
+> git clone https://github.com/flexferrum/Jinja2Cpp.git
+```
+
+3. Create build directory:
+
+```
+> cd Jinja2Cpp
+> mkdir build
+```
+
+4. Run CMake and build the library:
+
+```
+> cd build
+> cmake .. -DCMAKE_INSTALL_PREFIX=<path to install folder>
+> cmake --build . --target all
+```
+"Path to install folder" here is a path to the folder where you want to install Jinja2C++ lib.
+
+5. Install library:
+
+```
+> cmake --build . --target install
+```
+
+In this case, Jinja2C++ will be built with internally-shipped dependencies and install them respectively. But Jinja2C++ supports builds with externally-provided deps.
+### Usage with conan.io dependency manager
+Jinja2C++ can be used as conan.io package. In this case, you should do the following steps:
+
+1. Install conan.io according to the documentation ( https://docs.conan.io/en/latest/installation.html )
+2. Add a reference to Jinja2C++ package (`jinja2cpp/1.2.1`) to your conanfile.txt, conanfile.py or CMakeLists.txt. For instance, with the usage of `conan-cmake` integration it could be written this way:
+
+```cmake
+
+cmake_minimum_required(VERSION 3.24)
+project(Jinja2CppSampleConan CXX)
+
+list(APPEND CMAKE_MODULE_PATH ${CMAKE_BINARY_DIR})
+list(APPEND CMAKE_PREFIX_PATH ${CMAKE_BINARY_DIR})
+
+add_definitions("-std=c++14")
+
+if(NOT EXISTS "${CMAKE_BINARY_DIR}/conan.cmake")
+ message(STATUS "Downloading conan.cmake from https://github.com/conan-io/cmake-conan")
+ file(DOWNLOAD "https://raw.githubusercontent.com/conan-io/cmake-conan/0.18.1/conan.cmake"
+ "${CMAKE_BINARY_DIR}/conan.cmake"
+ TLS_VERIFY ON)
+endif()
+include(${CMAKE_BINARY_DIR}/conan.cmake)
+
+conan_cmake_autodetect(settings)
+conan_cmake_run(REQUIRES
+ jinja2cpp/1.1.0
+ gtest/1.14.0
+ BASIC_SETUP
+ ${CONAN_SETTINGS}
+ OPTIONS
+ jinja2cpp/*:shared=False
+ gtest/*:shared=False
+ BUILD missing)
+
+set (TARGET_NAME jinja2cpp_build_test)
+
+add_executable (${TARGET_NAME} main.cpp)
+
+target_link_libraries (${TARGET_NAME} ${CONAN_LIBS})
+set_target_properties (${TARGET_NAME} PROPERTIES
+ CXX_STANDARD 14
+ CXX_STANDARD_REQUIRED ON)
+
+```
+
+
+### Additional CMake build flags
+You can define (via -D command-line CMake option) the following build flags:
+
+- **JINJA2CPP_BUILD_TESTS** (default TRUE) - to build or not to Jinja2C++ tests.
+- **JINJA2CPP_STRICT_WARNINGS** (default TRUE) - Enable strict mode compile-warnings(-Wall -Werror, etc).
+- **JINJA2CPP_MSVC_RUNTIME_TYPE** (default /MD) - MSVC runtime type to link with (if you use Microsoft Visual Studio compiler).
+- **JINJA2CPP_DEPS_MODE** (default "internal") - modes for dependency handling. Following values possible:
+ - `internal` In this mode Jinja2C++ build script uses dependencies (include `boost`) shipped as subprojects. Nothing needs to be provided externally.
+ - `external-boost` In this mode Jinja2C++ build script uses only `boost` as an externally-provided dependency. All other dependencies are taken from subprojects.
+ - `external` In this mode all dependencies should be provided externally. Paths to `boost`, `nonstd-*` libs, etc. should be specified via standard CMake variables (like `CMAKE_PREFIX_PATH` or libname_DIR)
+ - `conan-build` Special mode for building Jinja2C++ via conan recipe.
+
+
+### Build with C++17 standard enabled
+Jinja2C++ tries to use standard versions of `std::variant`, `std::string_view` and `std::optional` if possible.
+
+## Acknowledgments
+Thanks to **@flexferrum** for creating this library, for being one of the brightest minds in software engineering community. Rest in peace, friend.
+
+Thanks to **@manu343726** for CMake scripts improvement, bug hunting, and fixing and conan.io packaging.
+
+Thanks to **@martinmoene** for the perfectly implemented xxx-lite libraries.
+
+Thanks to **@vitaut** for the amazing text formatting library.
+
+Thanks to **@martinus** for the fast hash maps implementation.
+
+
+## Changelog
+
+
+### Version 1.3.1
+
+#### Changes and improvements
+- bump deps versions
+- add new json binding - boost::json
+- speedup regex parsing by switching to boost::regex(std::regex extremely slow)
+ - templates are now loading faster
+
+#### Fixed bugs
+- small fixes across code base
+
+#### Breaking changes
+- internal deps now used through cmake fetch_content
+- default json serializer/deserializer is switched to boost::json
+
+### Version 1.2.1
+
+#### Changes and improvements
+- bump deps versions
+- support modern compilers(up to Clang 12) and standards(C++20)
+- tiny code style cleanup
+
+#### Fixed bugs
+- small fixes across code base
+
+#### Breaking changes
+- internal deps point to make based boost build
+
+### Version 1.1.0
+#### Changes and improvements
+- `batch` filter added
+- `slice` filter added
+- `format` filter added
+- `tojson` filter added
+- `striptags` filter added
+- `center` filter added
+- `xmlattr` filter added
+- `raw`/`endraw` tags added
+- repeat string operator added (e. g. `'a' * 5` will produce `'aaaaa'`)
+- support for templates metadata (`meta`/`endmeta` tags) added
+- `-fPIC` flag added to Linux build configuration
+
+#### Fixed bugs
+- Fix behavior of lstripblock/trimblocks global settings. Now it fully corresponds to the origina jinja2
+- Fix bug with rendering parent `block` content if child doesn't override this block
+- Fix compilation issues with user-defined callables with number of arguments more than 2
+- Fix access to global Jinja2 functions from included/extended templates
+- Fix point of evaluation of macro params
+- Fix looping over the strings
+- Cleanup warnings
+
+#### Breaking changes
+- From now with C++17 standard enabled Jinja2C++ uses standard versions of types `variant`, `string_view` and `optional`
+
+### Version 1.0.0
+#### Changes and improvements
+- `default` attribute added to the `map` filter (#48)
+- escape sequences support added to the string literals (#49)
+- arbitrary ranges, generated sequences, input iterators, etc. now can be used with `GenericList` type (#66)
+- nonstd::string_view is now one of the possible types for the `Value`
+- `filter` tag support added to the template parser (#44)
+- `escape` filter support added to the template parser (#140)
+- `capitalize` filter support added to the template parser (#137)
+- the multiline version of `set` tag added to the parser (#45)
+- added built-in reflection for nlohmann JSON and RapidJSON libraries (#78)
+- `loop.depth` and `loop.depth0` variables support added
+- {fmt} is now used as a formatting library instead of iostreams
+- robin hood hash map is now used for internal value storage
+- rendering performance improvements
+- template cache implemented in `TemplateEnv`
+- user-defined callables now can accept global context via `*context` special param
+- MinGW, clang >= 7.0, XCode >= 9, gcc >= 7.0 are now officially supported as a target compilers (#79)
+
+#### Fixed bugs
+- Fixed pipe (`|`) operator precedence (#47)
+- Fixed bug in internal char <-> wchar_t converter on Windows
+- Fixed crash in parsing `endblock` tag
+- Fixed scope control for `include` and `for` tags
+- Fixed bug with macros call within expression context
+
+#### Breaking changes
+- MSVC runtime type is now defined by `JINJA2CPP_MSVC_RUNTIME_TYPE` CMake variable
+
+### Version 0.9.2
+#### Major changes
+- User-defined callables implemented. Now you can define your own callable objects, pass them as input parameters and use them inside templates as regular (global) functions, filters or testers. See details here: https://jinja2cpp.github.io/docs/usage/ud_callables.html
+- Now you can define global (template environment-wide) parameters that are accessible for all templates bound to this environment.
+- `include`, `import` and `from` statements implemented. Now it's possible to include other templates and use macros from other templates.
+- `with` statement implemented
+- `do` statement implemented
+- Sample build projects for various Jinja2C++ usage variants created: https://github.com/jinja2cpp/examples-build](https://github.com/jinja2cpp/examples-build)
+- Documentation site created for Jinja2C++: https://jinja2cpp.github.io
+
+#### Minor changes
+- Render-time error handling added
+- Dependency management mode added to the build script
+- Fix bugs with error reporting during the parse time
+- Upgraded versions of external dependencies
+
+#### Breaking changes
+- `RenderAsString` method now returns `nonstd::expected` instead of regular `std::string`
+- Templates with `import`, `extends` and `include` generate errors if parsed without `TemplateEnv` set
+- Release bundles (archives) are configured with `external` dependency management mode by default
+
+### Version 0.9.1
+- `applymacro` filter added which allows applying arbitrary macro as a filter
+- dependencies to boost removed from the public interface
+- CMake scripts improved
+- Various bugs fixed
+- Improve reflection
+- Warnings cleanup
+
+### Version 0.9
+- Support of 'extents'/'block' statements
+- Support of 'macro'/'call' statements
+- Rich error reporting
+- Support for recursive loops
+- Support for space control before and after control blocks
+- Improve reflection
+
+### Version 0.6
+- A lot of filters have been implemented. Full set of supported filters listed here: [https://github.com/flexferrum/Jinja2Cpp/issues/7](https://github.com/flexferrum/Jinja2Cpp/issues/7)
+- A lot of testers have been implemented. Full set of supported testers listed here: [https://github.com/flexferrum/Jinja2Cpp/issues/8](https://github.com/flexferrum/Jinja2Cpp/issues/8)
+- 'Concatenate as string' operator ('~') has been implemented
+- For-loop with 'if' condition has been implemented
+- Fixed some bugs in parser
diff --git a/contrib/libs/jinja2cpp/include/jinja2cpp/binding/rapid_json.h b/contrib/libs/jinja2cpp/include/jinja2cpp/binding/rapid_json.h
new file mode 100644
index 0000000000..2d9bc47c1b
--- /dev/null
+++ b/contrib/libs/jinja2cpp/include/jinja2cpp/binding/rapid_json.h
@@ -0,0 +1,201 @@
+#ifndef JINJA2CPP_BINDING_RAPID_JSON_H
+#define JINJA2CPP_BINDING_RAPID_JSON_H
+
+#include <jinja2cpp/reflected_value.h>
+#include <jinja2cpp/string_helpers.h>
+
+#include <rapidjson/document.h>
+#include <rapidjson/rapidjson.h>
+
+namespace jinja2
+{
+namespace detail
+{
+
+template<typename CharT>
+struct RapidJsonNameConverter;
+
+template<>
+struct RapidJsonNameConverter<char>
+{
+ static const std::string& GetName(const std::string& str) { return str; }
+};
+
+template<>
+struct RapidJsonNameConverter<wchar_t>
+{
+ static std::wstring GetName(const std::string& str) { return ConvertString<std::wstring>(str); }
+};
+
+template<typename T>
+class RapidJsonObjectAccessor : public IMapItemAccessor, public ReflectedDataHolder<T, false>
+{
+public:
+ using ReflectedDataHolder<T, false>::ReflectedDataHolder;
+ using NameCvt = RapidJsonNameConverter<typename T::Ch>;
+ using ThisType = RapidJsonObjectAccessor<T>;
+ ~RapidJsonObjectAccessor() override = default;
+
+ size_t GetSize() const override
+ {
+ auto j = this->GetValue();
+ return j ? j->MemberCount() : 0ULL;
+ }
+
+ bool HasValue(const std::string& name) const override
+ {
+ auto j = this->GetValue();
+ return j ? j->HasMember(NameCvt::GetName(name).c_str()) : false;
+ }
+
+ Value GetValueByName(const std::string& nameOrig) const override
+ {
+ auto j = this->GetValue();
+ const auto& name = NameCvt::GetName(nameOrig);
+ if (!j || !j->HasMember(name.c_str()))
+ return Value();
+
+ return Reflect(&(*j)[name.c_str()]);
+ }
+
+ std::vector<std::string> GetKeys() const override
+ {
+ auto j = this->GetValue();
+ if (!j)
+ return {};
+
+ std::vector<std::string> result;
+ result.reserve(j->MemberCount());
+ for (auto it = j->MemberBegin(); it != j->MemberEnd(); ++ it)
+ {
+ result.emplace_back(ConvertString<std::string>(std::basic_string_view<typename T::Ch>(it->name.GetString())));
+ }
+ return result;
+ }
+
+ bool IsEqual(const IComparable& other) const override
+ {
+ auto* val = dynamic_cast<const ThisType*>(&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<typename Enc>
+struct RapidJsonArrayAccessor
+ : IListItemAccessor
+ , IIndexBasedAccessor
+ , ReflectedDataHolder<rapidjson::GenericValue<Enc>, false>
+{
+ using ReflectedDataHolder<rapidjson::GenericValue<Enc>, false>::ReflectedDataHolder;
+ using ThisType = RapidJsonArrayAccessor<Enc>;
+
+ std::optional<size_t> GetSize() const override
+ {
+ auto j = this->GetValue();
+ return j ? j->Size() : std::optional<size_t>();
+ }
+
+ const IIndexBasedAccessor* GetIndexer() const override
+ {
+ return this;
+ }
+
+ ListEnumeratorPtr CreateEnumerator() const override
+ {
+ using Enum = Enumerator<typename rapidjson::GenericValue<Enc>::ConstValueIterator>;
+ 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)[static_cast<rapidjson::SizeType>(idx)]);
+ }
+
+ bool IsEqual(const IComparable& other) const override
+ {
+ auto* val = dynamic_cast<const ThisType*>(&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<typename Enc>
+struct Reflector<rapidjson::GenericValue<Enc>>
+{
+ static Value CreateFromPtr(const rapidjson::GenericValue<Enc>* val)
+ {
+ Value result;
+ switch (val->GetType())
+ {
+ case rapidjson::kNullType:
+ break;
+ case rapidjson::kFalseType:
+ result = Value(false);
+ break;
+ case rapidjson::kTrueType:
+ result = Value(true);
+ break;
+ case rapidjson::kObjectType:
+ result = GenericMap([accessor = RapidJsonObjectAccessor<rapidjson::GenericValue<Enc>>(val)]() { return &accessor; });
+ break;
+ case rapidjson::kArrayType:
+ result = GenericList([accessor = RapidJsonArrayAccessor<Enc>(val)]() { return &accessor; });
+ break;
+ case rapidjson::kStringType:
+ result = std::basic_string<typename Enc::Ch>(val->GetString(), val->GetStringLength());
+ break;
+ case rapidjson::kNumberType:
+ if (val->IsInt64() || val->IsUint64())
+ result = val->GetInt64();
+ else if (val->IsInt() || val->IsUint())
+ result = val->GetInt();
+ else
+ result = val->GetDouble();
+ break;
+ }
+ return result;
+ }
+
+};
+
+template<typename Enc>
+struct Reflector<rapidjson::GenericDocument<Enc>>
+{
+ static Value Create(const rapidjson::GenericDocument<Enc>& val)
+ {
+ return GenericMap([accessor = RapidJsonObjectAccessor<rapidjson::GenericDocument<Enc>>(&val)]() { return &accessor; });
+ }
+
+ static Value CreateFromPtr(const rapidjson::GenericDocument<Enc>* val)
+ {
+ return GenericMap([accessor = RapidJsonObjectAccessor<rapidjson::GenericDocument<Enc>>(val)]() { return &accessor; });
+ }
+
+};
+} // namespace detail
+} // namespace jinja2
+
+#endif // JINJA2CPP_BINDING_RAPID_JSON_H
diff --git a/contrib/libs/jinja2cpp/include/jinja2cpp/config.h b/contrib/libs/jinja2cpp/include/jinja2cpp/config.h
new file mode 100644
index 0000000000..23d53ab33d
--- /dev/null
+++ b/contrib/libs/jinja2cpp/include/jinja2cpp/config.h
@@ -0,0 +1,27 @@
+#ifndef JINJA2CPP_CONFIG_H
+#define JINJA2CPP_CONFIG_H
+
+// The Jinja2C++ library version in the form major * 10000 + minor * 100 + patch.
+#define JINJA2CPP_VERSION 10100
+
+#ifdef _WIN32
+#define JINJA2_DECLSPEC(S) __declspec(S)
+#if _MSC_VER
+#pragma warning(disable : 4251)
+#endif
+#else
+#define JINJA2_DECLSPEC(S)
+#endif
+
+#ifdef JINJA2CPP_BUILD_AS_SHARED
+#define JINJA2CPP_EXPORT JINJA2_DECLSPEC(dllexport)
+#define JINJA2CPP_SHARED_LIB
+#elif JINJA2CPP_LINK_AS_SHARED
+#define JINJA2CPP_EXPORT JINJA2_DECLSPEC(dllimport)
+#define JINJA2CPP_SHARED_LIB
+#else
+#define JINJA2CPP_EXPORT
+#define JINJA2CPP_STATIC_LIB
+#endif
+
+#endif // JINJA2CPP_CONFIG_H
diff --git a/contrib/libs/jinja2cpp/include/jinja2cpp/error_info.h b/contrib/libs/jinja2cpp/include/jinja2cpp/error_info.h
new file mode 100644
index 0000000000..d4c2f88d96
--- /dev/null
+++ b/contrib/libs/jinja2cpp/include/jinja2cpp/error_info.h
@@ -0,0 +1,185 @@
+#ifndef JINJA2CPP_ERROR_INFO_H
+#define JINJA2CPP_ERROR_INFO_H
+
+#include "config.h"
+#include "value.h"
+
+#include <iostream>
+#include <type_traits>
+#include <vector>
+
+namespace jinja2
+{
+/*!
+ * \brief Type of the error
+ */
+enum class ErrorCode
+{
+ Unspecified = 0, //!< Error is unspecified
+ UnexpectedException = 1, //!< Generic exception occurred during template parsing or execution. ExtraParams[0] contains `what()` string of the exception
+ YetUnsupported, //!< Feature of the jinja2 specification which yet not supported
+ FileNotFound, //!< Requested file was not found. ExtraParams[0] contains name of the file
+ ExtensionDisabled, //!< Particular jinja2 extension disabled in the settings
+ TemplateEnvAbsent, //!< Template uses `extend`, `import`, `from` or `include` features but it's loaded without the template environment set
+ TemplateNotFound, //!< Template with the specified name was not found. ExtraParams[0] contains name of the file
+ TemplateNotParsed, //!< Template was not parsed
+ InvalidValueType, //!< Invalid type of the value in the particular context
+ InvalidTemplateName, //!< Invalid name of the template. ExtraParams[0] contains the name
+ MetadataParseError, //!< Invalid name of the template. ExtraParams[0] contains the name
+ ExpectedStringLiteral = 1001, //!< String literal expected
+ ExpectedIdentifier, //!< Identifier expected
+ ExpectedSquareBracket, //!< ']' expected
+ ExpectedRoundBracket, //!< ')' expected
+ ExpectedCurlyBracket, //!< '}' expected
+ ExpectedToken, //!< Specific token(s) expected. ExtraParams[0] contains the actual token, rest of ExtraParams contain set of expected tokens
+ ExpectedExpression, //!< Expression expected
+ ExpectedEndOfStatement, //!< End of statement expected. ExtraParams[0] contains the expected end of statement tag
+ ExpectedRawEnd, //!< {% endraw %} expected
+ ExpectedMetaEnd, //!< {% endmeta %} expected
+ UnexpectedToken, //!< Unexpected token. ExtraParams[0] contains the invalid token
+ UnexpectedStatement, //!< Unexpected statement. ExtraParams[0] contains the invalid statement tag
+ UnexpectedCommentBegin, //!< Unexpected comment block begin (`{#`)
+ UnexpectedCommentEnd, //!< Unexpected comment block end (`#}`)
+ UnexpectedExprBegin, //!< Unexpected expression block begin (`{{`)
+ UnexpectedExprEnd, //!< Unexpected expression block end (`}}`)
+ UnexpectedStmtBegin, //!< Unexpected statement block begin (`{%`)
+ UnexpectedStmtEnd, //!< Unexpected statement block end (`%}`)
+ UnexpectedRawBegin, //!< Unexpected raw block begin {% raw %}
+ UnexpectedRawEnd, //!< Unexpected raw block end {% endraw %}
+ UnexpectedMetaBegin, //!< Unexpected meta block begin {% meta %}
+ UnexpectedMetaEnd, //!< Unexpected meta block end {% endmeta %}
+};
+
+/*!
+ * \brief Information about the source location of the error
+ */
+struct SourceLocation
+{
+ //! Name of the file
+ std::string fileName;
+ //! Line number (1-based)
+ unsigned line = 0;
+ //! Column number (1-based)
+ unsigned col = 0;
+};
+
+template<typename CharT>
+/*!
+ * \brief Detailed information about the parse-time or render-time error
+ *
+ * If template parsing or rendering fails the detailed error information is provided. Exact specialization of ErrorInfoTpl is an object which contains
+ * this information. Type of specialization depends on type of the template object: \ref ErrorInfo for \ref Template and \ref ErrorInfoW for \ref TemplateW.
+ *
+ * Detailed information about an error contains:
+ * - Error code
+ * - Error location
+ * - Other locations related to the error
+ * - Description of the location
+ * - Extra parameters of the error
+ *
+ * @tparam CharT Character type which was used in template parser
+ */
+class ErrorInfoTpl
+{
+public:
+ struct Data
+ {
+ ErrorCode code = ErrorCode::Unspecified;
+ SourceLocation srcLoc;
+ std::vector<SourceLocation> relatedLocs;
+ std::vector<Value> extraParams;
+ std::basic_string<CharT> locationDescr;
+ };
+
+ //! Default constructor
+ ErrorInfoTpl() = default;
+ //! Initializing constructor from error description
+ explicit ErrorInfoTpl(Data data)
+ : m_errorData(std::move(data))
+ {}
+
+ //! Copy constructor
+ ErrorInfoTpl(const ErrorInfoTpl<CharT>&) = default;
+ //! Move constructor
+ ErrorInfoTpl(ErrorInfoTpl<CharT>&& val) noexcept
+ : m_errorData(std::move(val.m_errorData))
+ { }
+
+ //! Destructor
+ ~ErrorInfoTpl() noexcept = default;
+
+ //! Copy-assignment operator
+ ErrorInfoTpl& operator =(const ErrorInfoTpl<CharT>&) = default;
+ //! Move-assignment operator
+ ErrorInfoTpl& operator =(ErrorInfoTpl<CharT>&& val) noexcept
+ {
+ if (this == &val)
+ return *this;
+
+ std::swap(m_errorData.code, val.m_errorData.code);
+ std::swap(m_errorData.srcLoc, val.m_errorData.srcLoc);
+ std::swap(m_errorData.relatedLocs, val.m_errorData.relatedLocs);
+ std::swap(m_errorData.extraParams, val.m_errorData.extraParams);
+ std::swap(m_errorData.locationDescr, val.m_errorData.locationDescr);
+
+ return *this;
+ }
+
+ //! Return code of the error
+ ErrorCode GetCode() const
+ {
+ return m_errorData.code;
+ }
+
+ //! Return error location in the template file
+ auto& GetErrorLocation() const
+ {
+ return m_errorData.srcLoc;
+ }
+
+ //! Return locations, related to the main error location
+ auto& GetRelatedLocations() const
+ {
+ return m_errorData.relatedLocs;
+ }
+
+ /*!
+ * \brief Return location description
+ *
+ * Return string of two lines. First line is contents of the line with error. Second highlight the exact position within line. For instance:
+ * ```
+ * {% for i in range(10) endfor%}
+ * ---^-------
+ * ```
+ *
+ * @return Location description
+ */
+ const std::basic_string<CharT>& GetLocationDescr() const
+ {
+ return m_errorData.locationDescr;
+ }
+
+ /*!
+ * \brief Return extra params of the error
+ *
+ * Extra params is a additional details assiciated with the error. For instance, name of the file which wasn't opened
+ *
+ * @return Vector with extra error params
+ */
+ auto& GetExtraParams() const { return m_errorData.extraParams; }
+
+ //! Convert error to the detailed string representation
+ JINJA2CPP_EXPORT std::basic_string<CharT> ToString() const;
+
+private:
+ Data m_errorData;
+};
+
+using ErrorInfo = ErrorInfoTpl<char>;
+using ErrorInfoW = ErrorInfoTpl<wchar_t>;
+
+JINJA2CPP_EXPORT std::ostream& operator<<(std::ostream& os, const ErrorInfo& res);
+JINJA2CPP_EXPORT std::wostream& operator<<(std::wostream& os, const ErrorInfoW& res);
+} // namespace jinja2
+
+#endif // JINJA2CPP_ERROR_INFO_H
diff --git a/contrib/libs/jinja2cpp/include/jinja2cpp/filesystem_handler.h b/contrib/libs/jinja2cpp/include/jinja2cpp/filesystem_handler.h
new file mode 100644
index 0000000000..c33b4c9121
--- /dev/null
+++ b/contrib/libs/jinja2cpp/include/jinja2cpp/filesystem_handler.h
@@ -0,0 +1,201 @@
+#ifndef JINJA2CPP_FILESYSTEM_HANDLER_H
+#define JINJA2CPP_FILESYSTEM_HANDLER_H
+
+#include "config.h"
+
+#include <jinja2cpp/utils/i_comparable.h>
+
+#include <optional>
+#include <variant>
+
+#include <chrono>
+#include <iostream>
+#include <memory>
+#include <string>
+#include <unordered_map>
+
+namespace jinja2
+{
+
+template<typename CharT>
+using FileStreamPtr = std::unique_ptr<std::basic_istream<CharT>, void (*)(std::basic_istream<CharT>*)>;
+using CharFileStreamPtr = FileStreamPtr<char>;
+using WCharFileStreamPtr = FileStreamPtr<wchar_t>;
+
+/*!
+ * \brief Generic interface to filesystem handlers (loaders)
+ *
+ * This interface should be implemented in order to provide custom file system handler. Interface provides most-common methods which are called by
+ * the template environment to load the particular template. `OpenStream` methods return the unique pointer to the generic `istream` object implementation.
+ * 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 : public IComparable
+{
+public:
+ //! Destructor
+ virtual ~IFilesystemHandler() = default;
+
+ /*!
+ * \brief Method is called to open the file with the specified name in 'narrow-char' mode.
+ *
+ * Method should return unique pointer to the std::istream object with custom deleter (\ref CharFileStreamPtr) . Deleter should properly delete pointee
+ * stream object.
+ *
+ * @param name Name of the file to open
+ * @return Opened stream object or empty pointer in case of any error
+ */
+ virtual CharFileStreamPtr OpenStream(const std::string& name) const = 0;
+ /*!
+ * \brief Method is called to open the file with the specified name in 'wide-char' mode.
+ *
+ * Method should return unique pointer to the std::wistream object with custom deleter (\ref WCharFileStreamPtr) . Deleter should properly delete pointee
+ * stream object.
+ *
+ * @param name Name of the file to open
+ * @return Opened stream object or empty pointer in case of any error
+ */
+ virtual WCharFileStreamPtr OpenWStream(const std::string& name) const = 0;
+ /*!
+ * \brief Method is called to obtain the modification date of the specified file (if applicable)
+ *
+ * If the underlaying filesystem supports retrival of the last modification date of the file this method should return this date when called. In other
+ * case it should return the empty optional object. Main purpose of this method is to help templates loader to determine the necessity of cached template
+ * reload
+ *
+ * @param name Name of the file to get the last modification date
+ * @return Last modification date (if applicable) or empty optional object otherwise
+ */
+ virtual std::optional<std::chrono::system_clock::time_point> GetLastModificationDate(const std::string& name) const = 0;
+};
+
+using FilesystemHandlerPtr = std::shared_ptr<IFilesystemHandler>;
+
+/*!
+ * \brief Filesystem handler for files stored in memory
+ *
+ * This filesystem handler implements the simple dictionary object which maps name of the file to it's content. New files can be added by \ref AddFile
+ * methods. Content of the files automatically converted to narrow/wide strings representation if necessary.
+ */
+class JINJA2CPP_EXPORT MemoryFileSystem : public IFilesystemHandler
+{
+public:
+ /*!
+ * \brief Add new narrow-char "file" to the filesystem handler
+ *
+ * Adds new file entry to the internal dictionary object or overwrite the existing one. New entry contains the specified content of the file
+ *
+ * @param fileName Name of the file to add
+ * @param fileContent Content of the file to add
+ */
+ void AddFile(std::string fileName, std::string fileContent);
+ /*!
+ * \brief Add new wide-char "file" to the filesystem handler
+ *
+ * Adds new file entry to the internal dictionary object or overwrite the existing one. New entry contains the specified content of the file
+ *
+ * @param fileName Name of the file to add
+ * @param fileContent Content of the file to add
+ */
+ void AddFile(std::string fileName, std::wstring fileContent);
+
+ CharFileStreamPtr OpenStream(const std::string& name) const override;
+ WCharFileStreamPtr OpenWStream(const std::string& name) const override;
+ std::optional<std::chrono::system_clock::time_point> 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
+ {
+ std::optional<std::string> narrowContent;
+ std::optional<std::wstring> 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<std::string, FileContent> m_filesMap;
+};
+
+/*!
+ * \brief Filesystem handler for files stored on the filesystem
+ *
+ * This filesystem handler is an interface to the real file system. Root directory for file name mapping provided as a constructor argument. Each name (path) of
+ * the file to open is appended to the root directory path and then passed to the stream open methods.
+ */
+class JINJA2CPP_EXPORT RealFileSystem : public IFilesystemHandler
+{
+public:
+ /*!
+ * \brief Initializing/default constructor
+ *
+ * @param rootFolder Path to the root folder. This path is used as a root for every opened file
+ */
+ explicit RealFileSystem(std::string rootFolder = ".");
+
+ /*!
+ * \brief Reset path to the root folder to the new value
+ *
+ * @param newRoot New path to the root folder
+ */
+ void SetRootFolder(std::string newRoot)
+ {
+ m_rootFolder = std::move(newRoot);
+ }
+
+ /*!
+ * \brief Get path to the current root folder
+ *
+ * @return
+ */
+ const std::string& GetRootFolder() const
+ {
+ return m_rootFolder;
+ }
+ /*!
+ * \brief Get full path to the specified file.
+ *
+ * Appends specified name of the file to the current root folder and returns it
+ *
+ * @param name Name of the file to get path to
+ * @return Full path to the file
+ */
+ std::string GetFullFilePath(const std::string& name) const;
+
+ CharFileStreamPtr OpenStream(const std::string& name) const override;
+ WCharFileStreamPtr OpenWStream(const std::string& name) const override;
+ /*!
+ * \brief Open the specified file as a binary stream
+ *
+ * Opens the specified file in the binary mode (instead of text).
+ *
+ * @param name Name of the file to get the last modification date
+ * @return Last modification date (if applicable) or empty optional object otherwise
+ */
+ CharFileStreamPtr OpenByteStream(const std::string& name) const;
+ std::optional<std::chrono::system_clock::time_point> 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;
+};
+} // namespace jinja2
+
+#endif // JINJA2CPP_FILESYSTEM_HANDLER_H
diff --git a/contrib/libs/jinja2cpp/include/jinja2cpp/generic_list.h b/contrib/libs/jinja2cpp/include/jinja2cpp/generic_list.h
new file mode 100644
index 0000000000..ae70aa8e3e
--- /dev/null
+++ b/contrib/libs/jinja2cpp/include/jinja2cpp/generic_list.h
@@ -0,0 +1,287 @@
+#ifndef JINJA2CPP_GENERIC_LIST_H
+#define JINJA2CPP_GENERIC_LIST_H
+
+#include <jinja2cpp/utils/i_comparable.h>
+#include <jinja2cpp/value_ptr.h>
+
+#include <optional>
+
+#include <functional>
+#include <iterator>
+#include <memory>
+
+namespace jinja2
+{
+class Value;
+
+/*!
+ * \brief Interface for accessing items in list by the indexes
+ *
+ * This interface should provided by the particular list implementation in case of support index-based access to the items.
+ */
+struct IIndexBasedAccessor : virtual IComparable
+{
+ virtual ~IIndexBasedAccessor() = default;
+ /*!
+ * \brief This method is called to get the item by the specified index
+ *
+ * @param idx Index of item to get
+ *
+ * @return requested item
+ */
+ virtual Value GetItemByIndex(int64_t idx) const = 0;
+};
+
+struct IListEnumerator;
+using ListEnumeratorPtr = types::ValuePtr<IListEnumerator>;
+
+inline auto MakeEmptyListEnumeratorPtr()
+{
+ return ListEnumeratorPtr();
+}
+
+/*!
+ * \brief Generic list enumerator interface
+ *
+ * This interface should be implemented by the lists of any type. Interface is used to enumerate the list contents item by item.
+ *
+ * Implementation notes: Initial state of the enumerator should be "before the first item". So, the first call of \ref MoveNext method either moves the
+ * 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 IListEnumerator : virtual IComparable
+{
+ //! Destructor
+ virtual ~IListEnumerator() = default;
+
+ /*!
+ * \brief Method is called to reset enumerator to the initial state ('before the first element') if applicable.
+ *
+ * For the sequences which allow multi-pass iteration this method should reset enumerator to the initial state. For the single-pass sequences method
+ * can do nothing.
+ */
+ virtual void Reset() = 0;
+
+ /*!
+ * \brief Method is called to move the enumerator to the next item (if any)
+ *
+ * @return `true` if enumerator successfully moved and `false` if enumerator reaches the end of the sequence.
+ */
+ virtual bool MoveNext() = 0;
+
+ /*!
+ * \brief Method is called to get the current item value. Can be called multiply times
+ *
+ * @return Value of the item if the current item is valid item and empty value otherwise
+ */
+ virtual Value GetCurrent() const = 0;
+
+ /*!
+ * \brief Method is called to make a deep **copy** of the current enumerator state if possible
+ *
+ * @return New enumerator object with copy of the current enumerator state or empty pointer if copying is not applicable to the enumerator
+ */
+ virtual ListEnumeratorPtr Clone() const = 0;
+
+ /*!
+ * \brief Method is called to transfer current enumerator state to the new object
+ *
+ * State of the enumerator after successful creation of the new object is unspecified by there is a guarantee that there will no calls to the 'moved'
+ * enumerator. Destruction of the moved enumerator shouldn't affect the newly created object.
+ *
+ * @return New enumerator object which holds and owns the current enumerator state
+ */
+ virtual ListEnumeratorPtr Move() = 0;
+};
+
+/*!
+ * \brief Generic list implementation interface
+ *
+ * Every list implementation should implement this interface for providing access to the items of the list. There are several types of lists and implementation
+ * notes for every of them:
+ * - **Single-pass sequences** . For instance, input-stream iterators, generator-based sequences. This type of generic lists should provide no size and no indexer. Enumerator of such sequence supports should be moveable but non-copyable and non-resetable.
+ * - **Forward sequences** . For instance, single-linked lists. This type of lists should provide no size and indexer. Enumerator should be moveable, copyable and resetable.
+ * - **Bidirectional sequences**. Have got the same implementation requirements as forward sequences.
+ * - **Random-access sequences**. Such as arrays or vectors. Should provide valid size (number of stored items), valid indexer implementation and moveable,
+ * copyable and resetable enumerator.
+ *
+ * It's assumed that indexer interface is a part of list implementation.
+ */
+struct IListItemAccessor : virtual IComparable
+{
+ virtual ~IListItemAccessor() = default;
+
+ /*!
+ * \brief Called to get pointer to indexer interface implementation (if applicable)
+ *
+ * See implementation notes for the interface. This method should return pointer to the valid indexer interface if (and only if) list implementation
+ * supports random access to the items.
+ *
+ * Method can be called several times from the different threads.
+ *
+ * @return Pointer to the indexer interface implementation or null if indexing isn't supported for the list
+ */
+ virtual const IIndexBasedAccessor* GetIndexer() const = 0;
+
+ /*!
+ * \brief Called to get enumerator of the particular list
+ *
+ * See implementation notes for the interface. This method should return unique pointer (with custom deleter) to the ListEnumerator interface implementation. Enumerator implementation should follow the requirements for the particular list type implementation.
+ *
+ * Method can be called several times from the different threads.
+ *
+ * @return Pointer to the enumerator of the list
+ */
+ virtual ListEnumeratorPtr CreateEnumerator() const = 0;
+
+ /*!
+ * \brief Called to get size of the list if applicable.
+ *
+ * See implementation notes for the interface. This method should return valid (non-empty) size only for random-access sequences. In other cases this
+ * method should return empty optional.
+ *
+ * Method can be called several times from the different threads.
+ *
+ * @return Non-empty optional with the valid size of the list or empty optional in case of non-random sequence implementation
+ */
+ virtual std::optional<size_t> GetSize() const = 0;
+
+ /*!
+ * \brief Helper factory method of particular enumerator implementation
+ *
+ * @tparam T Type of enumerator to create
+ * @tparam Args Type of enumerator construct args
+ * @param args Actual enumerator constructor args
+ * @return Unique pointer to the enumerator
+ */
+ template<typename T, typename... Args>
+ static ListEnumeratorPtr MakeEnumerator(Args&&... args);
+};
+
+
+namespace detail
+{
+class GenericListIterator;
+} // namespace detail
+
+/*!
+ * \brief Facade class for generic lists
+ *
+ * This class holds the implementation of particular generic list interface and provides friendly access to it's method. Also this class is used to hold
+ * the particular list. Pointer to the generic list interface implementation is held inside std::function object which provides access to the pointer to the interface.
+ *
+ * You can use \ref MakeGenericList method to create instances of the GenericList:
+ * ```
+ * std::array<int, 9> sampleList{10, 20, 30, 40, 50, 60, 70, 80, 90};
+ *
+ * ValuesMap params = {
+ * {"input", jinja2::MakeGenericList(begin(sampleList), end(sampleList)) }
+ * };
+ * ```
+ */
+class JINJA2CPP_EXPORT GenericList
+{
+public:
+ //! Default constructor
+ GenericList() = default;
+
+ /*!
+ * \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 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:
+ *
+ * ```
+ * auto MakeGeneratedList(ListGenerator&& fn)
+ * {
+ * return GenericList([accessor = GeneratedListAccessor(std::move(fn))]() {return &accessor;});
+ * }
+ * ```
+ *
+ * @param accessor Functional object which provides access to the particular generic list implementation
+ */
+ explicit GenericList(std::function<const IListItemAccessor*()> accessor)
+ : m_accessor(std::move(accessor))
+ {
+ }
+
+ /*!
+ * \brief Get size of the list
+ *
+ * @return Actual size of the generic list or empty optional object if not applicable
+ */
+ std::optional<size_t> GetSize() const
+ {
+ return m_accessor ? m_accessor()->GetSize() : std::optional<size_t>();
+ }
+
+ /*!
+ * \brief Get pointer to the list accessor interface implementation
+ *
+ * @return Pointer to the list accessor interface or nullptr in case of non-initialized GenericList object
+ */
+ auto GetAccessor() const
+ {
+ return m_accessor ? m_accessor() : nullptr;
+ }
+
+ /*!
+ * \brief Check the GenericList object state
+ *
+ * @return true if GenericList object is valid (initialize) or false otherwize
+ */
+ bool IsValid() const
+ {
+ return !(!m_accessor);
+ }
+
+ /*!
+ * \brief Get interator to the first element of the list
+ *
+ * @return Iterator to the first element of the generic list or iterator equal to the `end()` if list is empty or not initialized
+ */
+ detail::GenericListIterator begin() const;
+ /*!
+ * \brief Get the end iterator
+ *
+ * @return 'end' iterator of the generic list
+ */
+ detail::GenericListIterator end() const;
+
+ /*!
+ * \brief Get interator to the first element of the list
+ *
+ * @return Iterator to the first element of the generic list or iterator equal to the `end()` if list is empty or not initialized
+ */
+ auto cbegin() const;
+ /*!
+ * \brief Get the end iterator
+ *
+ * @return 'end' iterator of the generic list
+ */
+ auto cend() const;
+
+ /*!
+ * \brief Compares with the objects of same type
+ *
+ * @return true if equal
+ */
+ bool IsEqual(const GenericList& rhs) const;
+
+private:
+ std::function<const IListItemAccessor*()> m_accessor;
+};
+
+bool operator==(const GenericList& lhs, const GenericList& rhs);
+bool operator!=(const GenericList& lhs, const GenericList& rhs);
+
+template<typename T, typename ...Args>
+inline ListEnumeratorPtr IListItemAccessor::MakeEnumerator(Args&& ...args)
+{
+ return ListEnumeratorPtr(types::MakeValuePtr<T>(std::forward<Args>(args)...));
+}
+} // namespace jinja2
+
+
+#endif // JINJA2CPP_GENERIC_LIST_H
diff --git a/contrib/libs/jinja2cpp/include/jinja2cpp/generic_list_iterator.h b/contrib/libs/jinja2cpp/include/jinja2cpp/generic_list_iterator.h
new file mode 100644
index 0000000000..7af469f8a3
--- /dev/null
+++ b/contrib/libs/jinja2cpp/include/jinja2cpp/generic_list_iterator.h
@@ -0,0 +1,104 @@
+#ifndef JINJA2CPP_GENERIC_LIST_ITERATOR_H
+#define JINJA2CPP_GENERIC_LIST_ITERATOR_H
+
+#include "generic_list.h"
+#include "value.h"
+#include "value_ptr.h"
+
+namespace jinja2
+{
+namespace detail
+{
+class JINJA2CPP_EXPORT GenericListIterator
+{
+public:
+ using iterator_category = std::input_iterator_tag;
+ using value_type = const Value;
+ using difference_type = std::ptrdiff_t;
+ using reference = const Value&;
+ using pointer = const Value*;
+ using EnumeratorPtr = types::ValuePtr<IListEnumerator>;
+
+ GenericListIterator() = default;
+
+ GenericListIterator(ListEnumeratorPtr enumerator)
+ : m_enumerator(types::ValuePtr<IListEnumerator>(enumerator))
+ {
+ if (m_enumerator)
+ m_hasValue = m_enumerator->MoveNext();
+
+ if (m_hasValue)
+ m_current = m_enumerator->GetCurrent();
+ }
+
+ bool operator == (const GenericListIterator& other) const
+ {
+ 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
+ {
+ return !(*this == other);
+ }
+
+ reference operator *() const
+ {
+ return m_current;
+ }
+
+ GenericListIterator& operator ++()
+ {
+ if (!m_enumerator)
+ return *this;
+ m_hasValue = m_enumerator->MoveNext();
+ if (m_hasValue)
+ {
+ m_current = m_enumerator->GetCurrent();
+ }
+ else
+ {
+ EnumeratorPtr temp;
+ Value tempVal;
+ using std::swap;
+ swap(m_enumerator, temp);
+ swap(m_current, tempVal);
+ }
+
+ return *this;
+ }
+
+ GenericListIterator operator++(int)
+ {
+ GenericListIterator result(std::move(m_current));
+
+ this->operator++();
+ return result;
+ }
+private:
+ explicit GenericListIterator(Value&& val)
+ : m_hasValue(true)
+ , m_current(std::move(val))
+ {
+
+ }
+
+private:
+ EnumeratorPtr m_enumerator;
+ bool m_hasValue = false;
+ Value m_current;
+};
+
+} // namespace detail
+} // namespace jinja2
+
+#endif // JINJA2CPP_GENERIC_LIST_ITERATOR_H
diff --git a/contrib/libs/jinja2cpp/include/jinja2cpp/polymorphic_value.h b/contrib/libs/jinja2cpp/include/jinja2cpp/polymorphic_value.h
new file mode 100644
index 0000000000..69663d859f
--- /dev/null
+++ b/contrib/libs/jinja2cpp/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 <cassert>
+#include <exception>
+#include <memory>
+#include <type_traits>
+#include <typeinfo>
+#include <utility>
+
+
+//
+// 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 <utility>:
+
+#if variant_CPP17_OR_GREATER
+
+#include <utility>
+
+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<T>
+#define nonstd_lite_in_place_index_t(K) std::in_place_index_t<K>
+
+#define nonstd_lite_in_place( T) std::in_place_t{}
+#define nonstd_lite_in_place_type( T) std::in_place_type_t<T>{}
+#define nonstd_lite_in_place_index(K) std::in_place_index_t<K>{}
+
+} // namespace nonstd
+
+#else // variant_CPP17_OR_GREATER
+
+#include <cstddef>
+
+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<T> = detail::in_place_type_tag<T>() )
+{
+ return in_place_t();
+}
+
+template< std::size_t K >
+inline in_place_t in_place( detail::in_place_index_tag<K> = detail::in_place_index_tag<K>() )
+{
+ return in_place_t();
+}
+
+template< class T >
+inline in_place_t in_place_type( detail::in_place_type_tag<T> = detail::in_place_type_tag<T>() )
+{
+ return in_place_t();
+}
+
+template< std::size_t K >
+inline in_place_t in_place_index( detail::in_place_index_tag<K> = detail::in_place_index_tag<K>() )
+{
+ return in_place_t();
+}
+
+// mimic templated typedef:
+
+#define nonstd_lite_in_place_t( T) nonstd::in_place_t(&)( nonstd::detail::in_place_type_tag<T> )
+#define nonstd_lite_in_place_type_t( T) nonstd::in_place_t(&)( nonstd::detail::in_place_type_tag<T> )
+#define nonstd_lite_in_place_index_t(K) nonstd::in_place_t(&)( nonstd::detail::in_place_index_tag<K> )
+
+#define nonstd_lite_in_place( T) nonstd::in_place_type<T>
+#define nonstd_lite_in_place_type( T) nonstd::in_place_type<T>
+#define nonstd_lite_in_place_index(K) nonstd::in_place_index<K>
+
+} // namespace nonstd
+
+#endif // variant_CPP17_OR_GREATER
+#endif // nonstd_lite_HAVE_IN_PLACE_TYPES
+
+namespace isocpp_p0201 {
+
+namespace detail {
+
+////////////////////////////////////////////////////////////////////////////
+// Implementation detail classes
+////////////////////////////////////////////////////////////////////////////
+
+template <class T>
+struct default_copy {
+ T* operator()(const T& t) const { return new T(t); }
+};
+
+template <class T>
+struct default_delete {
+ void operator()(const T* t) const { delete t; }
+};
+
+template <class T>
+struct control_block {
+ virtual ~control_block() = default;
+
+ virtual std::unique_ptr<control_block> clone() const = 0;
+
+ virtual T* ptr() = 0;
+};
+
+template <class T, class U = T>
+class direct_control_block : public control_block<T> {
+ static_assert(!std::is_reference<U>::value, "");
+ U u_;
+
+ public:
+ template <class... Ts>
+ explicit direct_control_block(Ts&&... ts) : u_(U(std::forward<Ts>(ts)...)) {}
+
+ std::unique_ptr<control_block<T>> clone() const override {
+ return std::make_unique<direct_control_block>(*this);
+ }
+
+ T* ptr() override { return std::addressof(u_); }
+};
+
+template <class T, class U, class C = default_copy<U>,
+ class D = default_delete<U>>
+class pointer_control_block : public control_block<T>, public C {
+ std::unique_ptr<U, D> 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<U, D> p, C c = C{})
+ : C(std::move(c)), p_(std::move(p)) {}
+
+ std::unique_ptr<control_block<T>> clone() const override {
+ assert(p_);
+ return std::make_unique<pointer_control_block>(
+ C::operator()(*p_), static_cast<const C&>(*this), p_.get_deleter());
+ }
+
+ T* ptr() override { return p_.get(); }
+};
+
+template <class T, class U>
+class delegating_control_block : public control_block<T> {
+ std::unique_ptr<control_block<U>> delegate_;
+
+ public:
+ explicit delegating_control_block(std::unique_ptr<control_block<U>> b)
+ : delegate_(std::move(b)) {}
+
+ std::unique_ptr<control_block<T>> clone() const override {
+ return std::make_unique<delegating_control_block>(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 T>
+class polymorphic_value;
+
+template <class T>
+struct is_polymorphic_value : std::false_type {};
+
+template <class T>
+struct is_polymorphic_value<polymorphic_value<T>> : std::true_type {};
+
+////////////////////////////////////////////////////////////////////////////////
+// `polymorphic_value` class definition
+////////////////////////////////////////////////////////////////////////////////
+
+template <class T>
+class polymorphic_value {
+ static_assert(!std::is_union<T>::value, "");
+ static_assert(std::is_class<T>::value, "");
+
+ template <class U>
+ friend class polymorphic_value;
+
+ template <class T_, class U, class... Ts>
+ friend polymorphic_value<T_> make_polymorphic_value(Ts&&... ts);
+ template <class T_, class... Ts>
+ friend polymorphic_value<T_> make_polymorphic_value(Ts&&... ts);
+
+ T* ptr_ = nullptr;
+ std::unique_ptr<detail::control_block<T>> cb_;
+
+ public:
+ //
+ // Destructor
+ //
+
+ ~polymorphic_value() = default;
+
+ //
+ // Constructors
+ //
+
+ polymorphic_value() {}
+
+ template <class U, class C = detail::default_copy<U>,
+ class D = detail::default_delete<U>,
+ class V = std::enable_if_t<std::is_convertible<U*, 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<D, detail::default_delete<U>>::value &&
+ std::is_same<C, detail::default_copy<U>>::value &&
+ typeid(*u) != typeid(U))
+ throw bad_polymorphic_value_construction();
+#endif
+ std::unique_ptr<U, D> p(u, std::move(deleter));
+
+ cb_ = std::make_unique<detail::pointer_control_block<T, U, C, D>>(
+ 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 <class U,
+ class V = std::enable_if_t<!std::is_same<T, U>::value &&
+ std::is_convertible<U*, T*>::value>>
+ explicit polymorphic_value(const polymorphic_value<U>& p) {
+ polymorphic_value<U> tmp(p);
+ ptr_ = tmp.ptr_;
+ cb_ = std::make_unique<detail::delegating_control_block<T, U>>(
+ std::move(tmp.cb_));
+ }
+
+ template <class U,
+ class V = std::enable_if_t<!std::is_same<T, U>::value &&
+ std::is_convertible<U*, T*>::value>>
+ explicit polymorphic_value(polymorphic_value<U>&& p) {
+ ptr_ = p.ptr_;
+ cb_ = std::make_unique<detail::delegating_control_block<T, U>>(
+ std::move(p.cb_));
+ p.ptr_ = nullptr;
+ }
+
+#if __cplusplus < 201703L
+
+#endif
+ //
+ // In-place constructor
+ //
+
+ template <class U,
+ class V = std::enable_if_t<
+ std::is_convertible<std::decay_t<U>*, T*>::value &&
+ !is_polymorphic_value<std::decay_t<U>>::value>,
+ class... Ts>
+ explicit polymorphic_value(nonstd_lite_in_place_type_t(U), Ts&&... ts)
+// explicit polymorphic_value(std::in_place_type_t<U>, Ts&&... ts)
+ : cb_(std::make_unique<detail::direct_control_block<T, U>>(
+ std::forward<Ts>(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 <class T, class... Ts>
+polymorphic_value<T> make_polymorphic_value(Ts&&... ts) {
+ polymorphic_value<T> p;
+ p.cb_ = std::make_unique<detail::direct_control_block<T, T>>(
+ std::forward<Ts>(ts)...);
+ p.ptr_ = p.cb_->ptr();
+ return p;
+}
+template <class T, class U, class... Ts>
+polymorphic_value<T> make_polymorphic_value(Ts&&... ts) {
+ polymorphic_value<T> p;
+ p.cb_ = std::make_unique<detail::direct_control_block<T, U>>(
+ std::forward<Ts>(ts)...);
+ p.ptr_ = p.cb_->ptr();
+ return p;
+}
+
+//
+// non-member swap
+//
+template <class T>
+void swap(polymorphic_value<T>& t, polymorphic_value<T>& u) noexcept {
+ t.swap(u);
+}
+
+} // namespace isocpp_p0201
+
+#endif // ISOCPP_P0201_POLYMORPHIC_VALUE_H_INCLUDED
diff --git a/contrib/libs/jinja2cpp/include/jinja2cpp/reflected_value.h b/contrib/libs/jinja2cpp/include/jinja2cpp/reflected_value.h
new file mode 100644
index 0000000000..fcb99a8031
--- /dev/null
+++ b/contrib/libs/jinja2cpp/include/jinja2cpp/reflected_value.h
@@ -0,0 +1,601 @@
+#ifndef JINJA2CPP_REFLECTED_VALUE_H
+#define JINJA2CPP_REFLECTED_VALUE_H
+
+#include "value.h"
+
+#include <optional>
+
+#include <cstddef>
+#include <memory>
+#include <set>
+#include <string>
+#include <type_traits>
+#include <vector>
+
+namespace jinja2
+{
+
+/*!
+ * \brief Reflect the arbitrary C++ type to the Jinja2C++ engine
+ *
+ * Generic method which reflects arbitrary C++ type to the Jinja2C++ template engine. The way of reflection depends on the actual reflected type and could be
+ * - Reflect as an exact value if the type is basic type (such as `char`, `int`, `double`, `std::string` etc.)
+ * - Reflect as a GenericList/GenericMap for standard containers respectively
+ * - Reflect as a GenericMap for the user types
+ * Also pointers/shared pointers to the types could be reflected.
+ *
+ * Reflected value takes ownership on the object, passed to the `Reflect` method by r-value reference or value. Actually, such object is moved. For const
+ * references or pointers reflected value holds the pointer to the reflected object. So, it's necessary to be sure that life time of the reflected object is
+ * longer than it's usage within the template.
+ *
+ * In order to reflect custom (user) the \ref jinja2::TypeReflection template should be specialized in the following way:
+ * ```c++
+ * struct jinja2::TypeReflection<TestStruct> : jinja2::TypeReflected<TestStruct>
+ * {
+ * using FieldAccessor = typename jinja2::TypeReflected<TestStruct>::FieldAccessor;
+ * static auto& GetAccessors()
+ * {
+ * static std::unordered_map<std::string, FieldAccessor> accessors = {
+ * {"intValue", [](const TestStruct& obj) {assert(obj.isAlive); return jinja2::Reflect(obj.intValue);}},
+ * {"intEvenValue", [](const TestStruct& obj) -> Value
+ * {
+ * assert(obj.isAlive);
+ * if (obj.intValue % 2)
+ * return {};
+ * return {obj.intValue};
+ * }},
+ * };
+ *
+ * return accessors;
+ * }
+ * };
+ *
+ * `TestStruct` here is a type which should be reflected. Specialization of the \ref TypeReflection template should derived from \ref TypeReflected template
+ * and define only one method: `GetAccessors`. This method returns the unordered map object which maps field name (as a string) to the corresponded field
+ * accessor. And field accessor here is a lambda object which takes the reflected object reference and returns the value of the field from it.
+ *
+ * @tparam T Type of value to reflect
+ * @param val Value to reflect
+ *
+ * @return jinja2::Value which contains the reflected value or the empty one
+ */
+template<typename T>
+Value Reflect(T&& val);
+
+template<typename T, bool val>
+struct TypeReflectedImpl : std::integral_constant<bool, val>
+{
+};
+
+template<typename T>
+using FieldAccessor = std::function<Value(const T&)>;
+
+template<typename T>
+struct TypeReflected : TypeReflectedImpl<T, true>
+{
+ using FieldAccessor = jinja2::FieldAccessor<T>;
+};
+
+
+
+template<typename T, typename = void>
+struct TypeReflection : TypeReflectedImpl<T, false>
+{
+};
+
+#ifndef JINJA2CPP_NO_DOXYGEN
+template<typename Derived>
+class ReflectedMapImplBase : public IMapItemAccessor
+{
+public:
+ bool HasValue(const std::string& name) const override
+ {
+ return Derived::GetAccessors().count(name) != 0;
+ }
+ Value GetValueByName(const std::string& name) const override
+ {
+ const auto& accessors = Derived::GetAccessors();
+ auto p = accessors.find(name);
+ if (p == accessors.end())
+ throw std::runtime_error("Invalid field access");
+
+ return static_cast<const Derived*>(this)->GetField(p->second);
+ }
+ std::vector<std::string> GetKeys() const override
+ {
+ std::vector<std::string> result;
+ const auto& accessors = Derived::GetAccessors();
+ for (auto& i : accessors)
+ result.push_back(i.first);
+
+ return result;
+ }
+ size_t GetSize() const override
+ {
+ return Derived::GetAccessors().size();
+ }
+};
+
+template<typename T, bool byValue = true>
+class ReflectedDataHolder;
+
+template<typename T>
+class ReflectedDataHolder<T, true>
+{
+public:
+ explicit ReflectedDataHolder(T val) : m_value(std::move(val)) {}
+ explicit ReflectedDataHolder(const T* val) : m_valuePtr(val) {}
+
+protected:
+ const T* GetValue() const
+ {
+ return m_valuePtr ? m_valuePtr : (m_value ? &m_value.value() : nullptr);
+ }
+
+private:
+ std::optional<T> m_value;
+ const T* m_valuePtr = nullptr;
+};
+
+template<typename T>
+class ReflectedDataHolder<T, false>
+{
+public:
+ explicit ReflectedDataHolder(const T* val) : m_valuePtr(val) {}
+
+protected:
+ const T* GetValue() const
+ {
+ return m_valuePtr;
+ }
+
+private:
+ const T* m_valuePtr = nullptr;
+};
+
+template<typename T>
+class ReflectedMapImpl : public ReflectedMapImplBase<ReflectedMapImpl<T>>, public ReflectedDataHolder<T>
+{
+public:
+ using ReflectedDataHolder<T>::ReflectedDataHolder;
+ using ThisType = ReflectedMapImpl<T>;
+
+ static auto GetAccessors() {return TypeReflection<T>::GetAccessors();}
+ template<typename Fn>
+ Value GetField(Fn&& accessor) const
+ {
+ auto v = this->GetValue();
+ if (!v)
+ return Value();
+ return accessor(*v);
+ }
+
+ bool IsEqual(const IComparable& other) const override
+ {
+ auto* val = dynamic_cast<const ThisType*>(&other);
+ if (!val)
+ return false;
+
+ return this->GetValue() == val->GetValue();
+ }
+};
+
+namespace detail
+{
+template<typename T, typename Tag = void>
+struct Reflector;
+
+template<typename T>
+using IsReflectedType = std::enable_if_t<TypeReflection<T>::value>;
+
+template<typename It>
+struct Enumerator : public IListEnumerator
+{
+ using ThisType = Enumerator<It>;
+ It m_begin;
+ It m_cur;
+ It m_end;
+ bool m_justInited = true;
+
+ Enumerator(It begin, It end)
+ : m_begin(begin)
+ , m_cur(end)
+ , m_end(end)
+ {}
+
+ void Reset() override
+ {
+ m_justInited = true;
+ }
+
+ bool MoveNext() override
+ {
+ if (m_justInited)
+ {
+ m_cur = m_begin;
+ m_justInited = false;
+ }
+ else
+ {
+ ++ m_cur;
+ }
+
+ return m_cur != m_end;
+ }
+
+ Value GetCurrent() const override
+ {
+ return Reflect(*m_cur);
+ }
+
+ ListEnumeratorPtr Clone() const override
+ {
+ auto result = std::make_unique<Enumerator<It>>(m_begin, m_end);
+ result->m_cur = m_cur;
+ result->m_justInited = m_justInited;
+ return jinja2::ListEnumeratorPtr(result.release()); //, Deleter);
+ }
+
+ ListEnumeratorPtr Move() override
+ {
+ auto result = std::make_unique<Enumerator<It>>(m_begin, m_end);
+ result->m_cur = std::move(m_cur);
+ result->m_justInited = m_justInited;
+ this->m_justInited = true;
+ return jinja2::ListEnumeratorPtr(result.release()); //, Deleter);
+ }
+
+ bool IsEqual(const IComparable& other) const override
+ {
+ auto* val = dynamic_cast<const ThisType*>(&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<Enumerator<It>*>(e);
+ }
+ */
+};
+
+struct ContainerReflector
+{
+ template<typename T>
+ struct ValueItemAccessor : IListItemAccessor, IIndexBasedAccessor
+ {
+ using ThisType = ValueItemAccessor<T>;
+
+ T m_value;
+
+ explicit ValueItemAccessor(T&& cont) noexcept
+ : m_value(std::move(cont))
+ {
+ }
+
+ std::optional<size_t> GetSize() const override
+ {
+ return m_value.size();
+ }
+
+ const IIndexBasedAccessor* GetIndexer() const override
+ {
+ return this;
+ }
+
+ ListEnumeratorPtr CreateEnumerator() const override
+ {
+ using Enum = Enumerator<typename T::const_iterator>;
+ return jinja2::ListEnumeratorPtr(new Enum(m_value.begin(), m_value.end()));//, Enum::Deleter);
+ }
+
+ Value GetItemByIndex(int64_t idx) const override
+ {
+ auto p = m_value.begin();
+ std::advance(p, static_cast<size_t>(idx));
+ return Reflect(*p);
+ }
+
+ bool IsEqual(const IComparable& other) const override
+ {
+ auto* val = dynamic_cast<const ThisType*>(&other);
+ if (!val)
+ return false;
+ auto enumerator = CreateEnumerator();
+ auto otherEnum = val->CreateEnumerator();
+ if (enumerator && otherEnum && !enumerator->IsEqual(*otherEnum))
+ return false;
+ return true;
+ }
+ };
+
+ template<typename T>
+ struct PtrItemAccessor : IListItemAccessor, IIndexBasedAccessor
+ {
+ using ThisType = PtrItemAccessor<T>;
+
+ const T* m_value{};
+
+ explicit PtrItemAccessor(const T* ptr)
+ : m_value(ptr)
+ {
+ }
+ std::optional<size_t> GetSize() const override
+ {
+ return m_value->size();
+ }
+ const IIndexBasedAccessor* GetIndexer() const override
+ {
+ return this;
+ }
+
+ ListEnumeratorPtr CreateEnumerator() const override
+ {
+ using Enum = Enumerator<typename T::const_iterator>;
+ return jinja2::ListEnumeratorPtr(new Enum(m_value->begin(), m_value->end()));//, Enum::Deleter);
+ }
+
+ Value GetItemByIndex(int64_t idx) const override
+ {
+ auto p = m_value->begin();
+ std::advance(p, static_cast<size_t>(idx));
+ return Reflect(*p);
+ }
+
+ bool IsEqual(const IComparable& other) const override
+ {
+ auto* val = dynamic_cast<const ThisType*>(&other);
+ if (!val)
+ return false;
+ auto enumerator = CreateEnumerator();
+ auto otherEnum = val->CreateEnumerator();
+ if (enumerator && otherEnum && !enumerator->IsEqual(*otherEnum))
+ return false;
+ return true;
+ }
+ };
+
+ template<typename T>
+ static Value CreateFromValue(T&& cont)
+ {
+ return GenericList([accessor = ValueItemAccessor<T>(std::forward<T>(cont))]() {return &accessor;});
+ }
+
+ template<typename T>
+ static Value CreateFromPtr(const T* cont)
+ {
+ return GenericList([accessor = PtrItemAccessor<T>(cont)]() {return &accessor;});
+ }
+
+ template<typename T>
+ static Value CreateFromPtr(std::shared_ptr<T> cont)
+ {
+ return GenericList([ptr = std::move(cont), accessor = PtrItemAccessor<T>(cont.get())]() {return &accessor;});
+ }
+};
+
+template<typename T>
+struct Reflector<std::set<T>>
+{
+ static auto Create(std::set<T> val)
+ {
+ return ContainerReflector::CreateFromValue(std::move(val));
+ }
+ static auto CreateFromPtr(const std::set<T>* val)
+ {
+ return ContainerReflector::CreateFromPtr(val);
+ }
+};
+
+template<typename T>
+struct Reflector<std::vector<T>>
+{
+ static auto Create(std::vector<T> val)
+ {
+ return ContainerReflector::CreateFromValue(std::move(val));
+ }
+ template<typename U>
+ static auto CreateFromPtr(U&& val)
+ {
+ return ContainerReflector::CreateFromPtr(std::forward<U>(val));
+ }
+};
+
+template<typename T>
+struct Reflector<T, IsReflectedType<T>>
+{
+ static auto Create(const T& val)
+ {
+ return GenericMap([accessor = ReflectedMapImpl<T>(val)]() {return &accessor;});
+ }
+
+ static auto CreateFromPtr(const T* val)
+ {
+ return GenericMap([accessor = ReflectedMapImpl<T>(static_cast<const T*>(val))]() {return &accessor;});
+ }
+
+ static auto CreateFromPtr(std::shared_ptr<T> val)
+ {
+ return GenericMap([ptr = val, accessor = ReflectedMapImpl<T>(val.get())]() {return &accessor;});
+ }
+};
+
+template<typename T>
+struct Reflector<const T&>
+{
+ static auto Create(const T& val)
+ {
+ return Reflector<T>::CreateFromPtr(&val);
+ }
+ static auto Create(const T*& val)
+ {
+ return Reflector<T>::CreateFromPtr(val);
+ }
+};
+
+template<typename T>
+struct Reflector<const T*&>
+{
+ static auto Create(const T*& val)
+ {
+ return Reflector<T>::CreateFromPtr(val);
+ }
+
+};
+
+template<typename T>
+struct Reflector<const T*const&>
+{
+ static auto Create(const T*const& val)
+ {
+ return Reflector<T>::CreateFromPtr(val);
+ }
+
+};
+
+template<typename T>
+struct Reflector<const std::shared_ptr<T>&>
+{
+ static auto Create(const std::shared_ptr<T>& val)
+ {
+ return Reflector<T>::CreateFromPtr(val.get());
+ }
+};
+
+template<typename T>
+struct Reflector<T&>
+{
+ static auto Create(T& val)
+ {
+ return Reflector<T>::Create(val);
+ }
+};
+
+template<typename T>
+struct Reflector<const T*>
+{
+ static auto Create(const T* val)
+ {
+ return Reflector<T>::CreateFromPtr(val);
+ }
+ static auto CreateFromPtr(const T* val)
+ {
+ return Reflector<T>::CreateFromPtr(val);
+ }
+};
+
+template<typename T>
+struct Reflector<T*>
+{
+ static auto Create(T* val)
+ {
+ return Reflector<T>::CreateFromPtr(val);
+ }
+};
+
+template<typename T>
+struct Reflector<std::shared_ptr<T>>
+{
+ static auto Create(std::shared_ptr<T> val)
+ {
+ return Reflector<T>::CreateFromPtr(val);
+ }
+};
+
+template<typename CharT>
+struct Reflector<std::basic_string<CharT>>
+{
+ static auto Create(std::basic_string<CharT> str) {
+ return Value(std::move(str));
+ }
+ static auto CreateFromPtr(const std::basic_string<CharT>* str) {
+ return Value(*str);
+ }
+};
+
+template<typename CharT>
+struct Reflector<std::basic_string_view<CharT>>
+{
+ static auto Create(std::basic_string_view<CharT> str) { return Value(std::move(str)); }
+ static auto CreateFromPtr(const std::basic_string_view<CharT>* str) { return Value(*str); }
+};
+
+template<>
+struct Reflector<bool>
+{
+ static auto Create(bool val)
+ {
+ return Value(val);
+ }
+ static auto CreateFromPtr(const bool* val)
+ {
+ return Value(*val);
+ }
+};
+
+template<>
+struct Reflector<float>
+{
+ static auto Create(double val) { return Value(val); }
+ static auto CreateFromPtr(const float* val) { return Value(static_cast<double>(*val)); }
+};
+
+template<>
+struct Reflector<double>
+{
+ static auto Create(double val) { return Value(val); }
+ static auto CreateFromPtr(const double* val) { return Value(*val); }
+};
+
+#define JINJA2_INT_REFLECTOR(Type) \
+template<> \
+struct Reflector<Type> \
+{ \
+ static auto Create(Type val) \
+ { \
+ return Value(static_cast<int64_t>(val)); \
+ } \
+ static auto CreateFromPtr(const Type* val) \
+ { \
+ return Value(static_cast<int64_t>(*val)); \
+ } \
+}
+
+JINJA2_INT_REFLECTOR(char);
+JINJA2_INT_REFLECTOR(wchar_t);
+JINJA2_INT_REFLECTOR(int8_t);
+JINJA2_INT_REFLECTOR(uint8_t);
+JINJA2_INT_REFLECTOR(int16_t);
+JINJA2_INT_REFLECTOR(uint16_t);
+JINJA2_INT_REFLECTOR(int32_t);
+JINJA2_INT_REFLECTOR(uint32_t);
+JINJA2_INT_REFLECTOR(int64_t);
+JINJA2_INT_REFLECTOR(uint64_t);
+} // namespace detail
+#endif
+
+template<typename T>
+Value Reflect(T&& val)
+{
+ return detail::Reflector<T>::Create(std::forward<T>(val));
+}
+
+} // namespace jinja2
+
+#endif // JINJA2CPP_REFLECTED_VALUE_H
diff --git a/contrib/libs/jinja2cpp/include/jinja2cpp/string_helpers.h b/contrib/libs/jinja2cpp/include/jinja2cpp/string_helpers.h
new file mode 100644
index 0000000000..b2bb68872c
--- /dev/null
+++ b/contrib/libs/jinja2cpp/include/jinja2cpp/string_helpers.h
@@ -0,0 +1,296 @@
+#ifndef JINJA2CPP_STRING_HELPERS_H
+#define JINJA2CPP_STRING_HELPERS_H
+
+#include "value.h"
+
+#include <string_view>
+
+#include <cwchar>
+#include <string>
+#include <type_traits>
+
+namespace jinja2
+{
+namespace detail
+{
+ template<typename Src, typename Dst>
+ struct StringConverter;
+
+ template<typename Src>
+ struct StringConverter<Src, Src>
+ {
+ static Src DoConvert(const std::basic_string_view<typename Src::value_type>& from)
+ {
+ return Src(from.begin(), from.end());
+ }
+ };
+
+ template<>
+ struct StringConverter<std::wstring, std::string>
+ {
+ static std::string DoConvert(const std::wstring_view& from)
+ {
+ std::mbstate_t state = std::mbstate_t();
+ auto srcPtr = from.data();
+ std::size_t srcSize = from.size();
+ std::size_t destBytes = 0;
+
+#ifndef _MSC_VER
+ destBytes = std::wcsrtombs(nullptr, &srcPtr, srcSize, &state);
+ if (destBytes == static_cast<std::size_t>(-1))
+ return std::string();
+#else
+ auto err = wcsrtombs_s(&destBytes, nullptr, 0, &srcPtr, srcSize, &state);
+ if (err != 0)
+ return std::string();
+#endif
+ std::string result;
+#ifndef _MSC_VER
+ result.resize(destBytes + 1);
+ auto converted = std::wcsrtombs(&result[0], &srcPtr, srcSize, &state);
+ if (converted == static_cast<std::size_t>(-1))
+ return std::string();
+ result.resize(converted);
+#else
+ result.resize(destBytes);
+ wcsrtombs_s(&destBytes, &result[0], destBytes, &srcPtr, srcSize, &state);
+ result.resize(destBytes - 1);
+#endif
+ return result;
+ }
+ };
+
+ template<>
+ struct StringConverter<std::string, std::wstring>
+ {
+ static std::wstring DoConvert(const std::string_view& from)
+ {
+ std::mbstate_t state = std::mbstate_t();
+ auto srcPtr = from.data();
+ std::size_t srcSize = from.size();
+ std::size_t destBytes = 0;
+
+#ifndef _MSC_VER
+ destBytes = std::mbsrtowcs(nullptr, &srcPtr, srcSize, &state);
+ if (destBytes == static_cast<std::size_t>(-1))
+ return std::wstring();
+#else
+ auto err = mbsrtowcs_s(&destBytes, nullptr, 0, &srcPtr, srcSize, &state);
+ if (err != 0)
+ return std::wstring();
+#endif
+ std::wstring result;
+#ifndef _MSC_VER
+ result.resize(destBytes + 1);
+ srcPtr = from.data();
+ auto converted = std::mbsrtowcs(&result[0], &srcPtr, srcSize, &state);
+ if (converted == static_cast<std::size_t>(-1))
+ return std::wstring();
+ result.resize(converted);
+#else
+ result.resize(destBytes);
+ mbsrtowcs_s(&destBytes, &result[0], destBytes, &srcPtr, srcSize, &state);
+ result.resize(destBytes - 1);
+#endif
+ return result;
+ }
+ };
+
+ template<typename CharT, typename T>
+ struct StringConverter<std::basic_string_view<CharT>, T> : public StringConverter<std::basic_string<CharT>, T> {};
+
+} // namespace detail
+
+/*!
+ * \brief Convert string objects from one representation or another
+ *
+ * Converts string or string views to string objects with possible char conversion (char -> wchar_t or wchar_t -> char).
+ * This function should be used when exact type of string is needed.
+ *
+ * @tparam Dst Destination string type. Mandatory. Can be std::string or std::wstring
+ * @tparam Src Source string type. Auto detected. Can be either std::basic_string<CharT> or std::string_view<CharT>
+ *
+ * @param from Source string object which should be converted
+ * @return Destination string object of the specified type
+ */
+template<typename Dst, typename Src>
+Dst ConvertString(Src&& from)
+{
+ using src_t = std::decay_t<Src>;
+ return detail::StringConverter<src_t, std::decay_t<Dst>>::DoConvert(std::basic_string_view<typename src_t::value_type>(from));
+}
+
+/*!
+ * \brief Gets std::string from std::string
+ *
+ * Helper method for use in template context which gets std::string from the other possible string objects (std::string in this case)
+ *
+ * @param str Source string
+ * @return Copy of the source string
+ */
+inline const std::string AsString(const std::string& str)
+{
+ return str;
+}
+/*!
+ * \brief Gets std::string from std::wstring
+ *
+ * Helper method for use in template context which gets std::string from the other possible string objects (std::wstring in this case)
+ * Conversion wchar_t -> char is performing
+ *
+ * @param str Source string
+ * @return Converted source string
+ */
+inline std::string AsString(const std::wstring& str)
+{
+ return ConvertString<std::string>(str);
+}
+/*!
+ * \brief Gets std::string from std::string_view
+ *
+ * Helper method for use in template context which gets std::string from the other possible string objects (std::string_view in this case)
+ *
+ * @param str Source string
+ * @return Copy of the source string
+ */
+inline std::string AsString(const std::string_view& str)
+{
+ return std::string(str.begin(), str.end());
+}
+/*!
+ * \brief Gets std::string from std::wstring_view
+ *
+ * Helper method for use in template context which gets std::string from the other possible string objects (std::wstring_view in this case)
+ * Conversion wchar_t -> char is performing
+ *
+ * @param str Source string
+ * @return Converted source string
+ */
+inline std::string AsString(const std::wstring_view& str)
+{
+ return ConvertString<std::string>(str);
+}
+/*!
+ * \brief Gets std::wstring from std::wstring
+ *
+ * Helper method for use in template context which gets std::wstring from the other possible string objects (std::wstring in this case)
+ *
+ * @param str Source string
+ * @return Copy of the source string
+ */
+inline const std::wstring AsWString(const std::wstring& str)
+{
+ return str;
+}
+/*!
+ * \brief Gets std::wstring from std::string
+ *
+ * Helper method for use in template context which gets std::wstring from the other possible string objects (std::string in this case)
+ * Conversion char -> wchar_t is performing
+ *
+ * @param str Source string
+ * @return Converted source string
+ */
+inline std::wstring AsWString(const std::string& str)
+{
+ return ConvertString<std::wstring>(str);
+}
+/*!
+ * \brief Gets std::wstring from std::wstring_view
+ *
+ * Helper method for use in template context which gets std::wstring from the other possible string objects (std::wstring_view in this case)
+ *
+ * @param str Source string
+ * @return Copy of the source string
+ */
+inline std::wstring AsWString(const std::wstring_view& str)
+{
+ return std::wstring(str.begin(), str.end());
+}
+/*!
+ * \brief Gets std::wstring from std::string_view
+ *
+ * Helper method for use in template context which gets std::wstring from the other possible string objects (std::string_view in this case)
+ * Conversion char -> wchar_t is performing
+ *
+ * @param str Source string
+ * @return Converted source string
+ */
+inline std::wstring AsWString(const std::string_view& str)
+{
+ return ConvertString<std::wstring>(str);
+}
+
+namespace detail
+{
+struct StringGetter
+{
+ template<typename CharT>
+ std::string operator()(const std::basic_string<CharT>& str) const
+ {
+ return AsString(str);
+ }
+ template<typename CharT>
+ std::string operator()(const std::basic_string_view<CharT>& str) const
+ {
+ return AsString(str);
+ }
+
+ template<typename T>
+ std::string operator()(T&&) const
+ {
+ return std::string();
+ }
+};
+
+struct WStringGetter
+{
+ template<typename CharT>
+ std::wstring operator()(const std::basic_string<CharT>& str) const
+ {
+ return AsWString(str);
+ }
+ template<typename CharT>
+ std::wstring operator()(const std::basic_string_view<CharT>& str) const
+ {
+ return AsWString(str);
+ }
+
+ template<typename T>
+ std::wstring operator()(T&&) const
+ {
+ return std::wstring();
+ }
+};
+} // namespace detail
+/*!
+ * \brief Gets std::string from the arbitrary \ref Value
+ *
+ * Helper method for use in template context which gets std::string from the other possible string objects (Value in this case).
+ * Conversion wchar_t -> char is performing if needed. In case of non-string object actually stored in the \ref Value
+ * empty string is returned.
+ *
+ * @param val Source string
+ * @return Extracted or empty string
+ */
+inline std::string AsString(const Value& val)
+{
+ return std::visit(detail::StringGetter(), val.data());
+}
+/*!
+ * \brief Gets std::wstring from the arbitrary \ref Value
+ *
+ * Helper method for use in template context which gets std::wstring from the other possible string objects (Value in this case).
+ * Conversion char -> wchar_t is performing if needed. In case of non-string object actually stored in the \ref Value
+ * empty string is returned.
+ *
+ * @param val Source string
+ * @return Extracted or empty string
+ */
+inline std::wstring AsWString(const Value& val)
+{
+ return std::visit(detail::WStringGetter(), val.data());
+}
+} // namespace jinja2
+
+#endif // JINJA2CPP_STRING_HELPERS_H
diff --git a/contrib/libs/jinja2cpp/include/jinja2cpp/template.h b/contrib/libs/jinja2cpp/include/jinja2cpp/template.h
new file mode 100644
index 0000000000..889dbd2ae3
--- /dev/null
+++ b/contrib/libs/jinja2cpp/include/jinja2cpp/template.h
@@ -0,0 +1,305 @@
+#ifndef JINJA2CPP_TEMPLATE_H
+#define JINJA2CPP_TEMPLATE_H
+
+#include "config.h"
+#include "error_info.h"
+#include "value.h"
+
+#include <contrib/restricted/expected-lite/include/nonstd/expected.hpp>
+
+#include <iostream>
+#include <memory>
+#include <string>
+
+namespace jinja2
+{
+class JINJA2CPP_EXPORT ITemplateImpl;
+class JINJA2CPP_EXPORT TemplateEnv;
+template<typename CharT>
+class TemplateImpl;
+template<typename U>
+using Result = nonstd::expected<U, ErrorInfo>;
+template<typename U>
+using ResultW = nonstd::expected<U, ErrorInfoW>;
+
+template<typename CharT>
+struct MetadataInfo
+{
+ std::string metadataType;
+ std::basic_string_view<CharT> metadata;
+ SourceLocation location;
+};
+
+/*!
+ * \brief Template object which is used to render narrow char templates
+ *
+ * This class is a main class for rendering narrow char templates. It can be used independently or together with
+ * \ref TemplateEnv. In the second case it's possible to use templates inheritance and extension.
+ *
+ * Basic usage of Template class:
+ * ```c++
+ * std::string source = "Hello World from Parser!";
+ *
+ * jinja2::Template tpl;
+ * tpl.Load(source);
+ * std::string result = tpl.RenderAsString(ValuesMap{}).value();
+ * ```
+ */
+class JINJA2CPP_EXPORT Template
+{
+public:
+ /*!
+ * \brief Default constructor
+ */
+ Template()
+ : Template(nullptr)
+ {
+ }
+ /*!
+ * \brief Initializing constructor
+ *
+ * Creates instance of the template with the specified template environment object
+ *
+ * @param env Template environment object which created template should refer to
+ */
+ explicit Template(TemplateEnv* env);
+ /*!
+ * Destructor
+ */
+ ~Template();
+
+ /*!
+ * \brief Load template from the zero-terminated narrow char string
+ *
+ * Takes specified narrow char string and parses it as a Jinja2 template. In case of error returns detailed
+ * diagnostic
+ *
+ * @param tpl Zero-terminated narrow char string with template description
+ * @param tplName Optional name of the template (for the error reporting purposes)
+ *
+ * @return Either noting or instance of \ref ErrorInfoTpl as an error
+ */
+ Result<void> Load(const char* tpl, std::string tplName = std::string());
+ /*!
+ * \brief Load template from the std::string
+ *
+ * Takes specified std::string object and parses it as a Jinja2 template. In case of error returns detailed
+ * diagnostic
+ *
+ * @param str std::string object with template description
+ * @param tplName Optional name of the template (for the error reporting purposes)
+ *
+ * @return Either noting or instance of \ref ErrorInfoTpl as an error
+ */
+ Result<void> Load(const std::string& str, std::string tplName = std::string());
+ /*!
+ * \brief Load template from the stream
+ *
+ * Takes specified stream object and parses it as a source of Jinja2 template. In case of error returns detailed
+ * diagnostic
+ *
+ * @param stream Stream object with template description
+ * @param tplName Optional name of the template (for the error reporting purposes)
+ *
+ * @return Either noting or instance of \ref ErrorInfoTpl as an error
+ */
+ Result<void> Load(std::istream& stream, std::string tplName = std::string());
+ /*!
+ * \brief Load template from the specified file
+ *
+ * Loads file with the specified name and parses it as a source of Jinja2 template. In case of error returns
+ * detailed diagnostic
+ *
+ * @param fileName Name of the file to load
+ *
+ * @return Either noting or instance of \ref ErrorInfoTpl as an error
+ */
+ Result<void> LoadFromFile(const std::string& fileName);
+
+ /*!
+ * \brief Render previously loaded template to the narrow char stream
+ *
+ * Renders previously loaded template to the specified narrow char stream and specified set of params.
+ *
+ * @param os Stream to render template to
+ * @param params Set of params which should be passed to the template engine and can be used within the template
+ *
+ * @return Either noting or instance of \ref ErrorInfoTpl as an error
+ */
+ Result<void> Render(std::ostream& os, const ValuesMap& params);
+ /*!
+ * \brief Render previously loaded template to the narrow char string
+ *
+ * Renders previously loaded template as a narrow char string and with specified set of params.
+ *
+ * @param params Set of params which should be passed to the template engine and can be used within the template
+ *
+ * @return Either rendered string or instance of \ref ErrorInfoTpl as an error
+ */
+ Result<std::string> RenderAsString(const ValuesMap& params);
+ /*!
+ * \brief Get metadata, provided in the {% meta %} tag
+ *
+ * @return Parsed metadata as a generic map value or instance of \ref ErrorInfoTpl as an error
+ */
+ Result<GenericMap> GetMetadata();
+ /*!
+ * \brief Get non-parsed metadata, provided in the {% meta %} tag
+ *
+ * @return Non-parsed metadata information or instance of \ref ErrorInfoTpl as an error
+ */
+ Result<MetadataInfo<char>> 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<ITemplateImpl> m_impl;
+ friend class TemplateImpl<char>;
+};
+
+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
+ *
+ * This class is a main class for rendering wide char templates. It can be used independently or together with
+ * \ref TemplateEnv. In the second case it's possible to use templates inheritance and extension.
+ *
+ * Basic usage of Template class:
+ * ```c++
+ * std::string source = "Hello World from Parser!";
+ *
+ * jinja2::Template tpl;
+ * tpl.Load(source);
+ * std::string result = tpl.RenderAsString(ValuesMap{}).value();
+ * ```
+*/
+class JINJA2CPP_EXPORT TemplateW
+{
+public:
+ /*!
+ * \brief Default constructor
+ */
+ TemplateW()
+ : TemplateW(nullptr)
+ {
+ }
+ /*!
+ * \brief Initializing constructor
+ *
+ * Creates instance of the template with the specified template environment object
+ *
+ * @param env Template environment object which created template should refer to
+ */
+ explicit TemplateW(TemplateEnv* env);
+ /*!
+ * Destructor
+ */
+ ~TemplateW();
+
+ /*!
+ * \brief Load template from the zero-terminated wide char string
+ *
+ * Takes specified wide char string and parses it as a Jinja2 template. In case of error returns detailed
+ * diagnostic
+ *
+ * @param tpl Zero-terminated wide char string with template description
+ * @param tplName Optional name of the template (for the error reporting purposes)
+ *
+ * @return Either noting or instance of \ref ErrorInfoTpl as an error
+ */
+ ResultW<void> Load(const wchar_t* tpl, std::string tplName = std::string());
+ /*!
+ * \brief Load template from the std::wstring
+ *
+ * Takes specified std::wstring object and parses it as a Jinja2 template. In case of error returns detailed
+ * diagnostic
+ *
+ * @param str std::wstring object with template description
+ * @param tplName Optional name of the template (for the error reporting purposes)
+ *
+ * @return Either noting or instance of \ref ErrorInfoTpl as an error
+ */
+ ResultW<void> Load(const std::wstring& str, std::string tplName = std::string());
+ /*!
+ * \brief Load template from the stream
+ *
+ * Takes specified stream object and parses it as a source of Jinja2 template. In case of error returns detailed
+ * diagnostic
+ *
+ * @param stream Stream object with template description
+ * @param tplName Optional name of the template (for the error reporting purposes)
+ *
+ * @return Either noting or instance of \ref ErrorInfoTpl as an error
+ */
+ ResultW<void> Load(std::wistream& stream, std::string tplName = std::string());
+ /*!
+ * \brief Load template from the specified file
+ *
+ * Loads file with the specified name and parses it as a source of Jinja2 template. In case of error returns
+ * detailed diagnostic
+ *
+ * @param fileName Name of the file to load
+ *
+ * @return Either noting or instance of \ref ErrorInfoTpl as an error
+ */
+ ResultW<void> LoadFromFile(const std::string& fileName);
+
+ /*!
+ * \brief Render previously loaded template to the wide char stream
+ *
+ * Renders previously loaded template to the specified wide char stream and specified set of params.
+ *
+ * @param os Stream to render template to
+ * @param params Set of params which should be passed to the template engine and can be used within the template
+ *
+ * @return Either noting or instance of \ref ErrorInfoTpl as an error
+ */
+ ResultW<void> Render(std::wostream& os, const ValuesMap& params);
+ /*!
+ * \brief Render previously loaded template to the wide char string
+ *
+ * Renders previously loaded template as a wide char string and with specified set of params.
+ *
+ * @param params Set of params which should be passed to the template engine and can be used within the template
+ *
+ * @return Either rendered string or instance of \ref ErrorInfoTpl as an error
+ */
+ ResultW<std::wstring> RenderAsString(const ValuesMap& params);
+ /*!
+ * \brief Get metadata, provided in the {% meta %} tag
+ *
+ * @return Parsed metadata as a generic map value or instance of \ref ErrorInfoTpl as an error
+ */
+ ResultW<GenericMap> GetMetadata();
+ /*!
+ * \brief Get non-parsed metadata, provided in the {% meta %} tag
+ *
+ * @return Non-parsed metadata information or instance of \ref ErrorInfoTpl as an error
+ */
+ ResultW<MetadataInfo<wchar_t>> 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<ITemplateImpl> m_impl;
+ friend class TemplateImpl<wchar_t>;
+};
+
+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/contrib/libs/jinja2cpp/include/jinja2cpp/template_env.h b/contrib/libs/jinja2cpp/include/jinja2cpp/template_env.h
new file mode 100644
index 0000000000..80dcecc1ec
--- /dev/null
+++ b/contrib/libs/jinja2cpp/include/jinja2cpp/template_env.h
@@ -0,0 +1,320 @@
+#ifndef JINJA2CPP_TEMPLATE_ENV_H
+#define JINJA2CPP_TEMPLATE_ENV_H
+
+#include "config.h"
+#include "error_info.h"
+#include "filesystem_handler.h"
+#include "template.h"
+
+#include <mutex>
+#include <shared_mutex>
+#include <unordered_map>
+
+namespace jinja2
+{
+
+class IErrorHandler;
+class IFilesystemHandler;
+
+//! Compatibility mode for jinja2c++ engine
+enum class Jinja2CompatMode
+{
+ None, //!< Default mode
+ Vesrsion_2_10, //!< Compatibility with Jinja2 v.2.10 specification
+};
+
+//! Global template environment settings
+struct Settings
+{
+ /// Extensions set which should be supported
+ struct Extensions
+ {
+ bool Do = false; //!< Enable use of `do` statement
+ };
+
+ //! Enables use of line statements (yet not supported)
+ bool useLineStatements = false;
+ //! Enables blocks trimming the same way as it does python Jinja2 engine
+ bool trimBlocks = false;
+ //! Enables blocks stripping (from the left) the same way as it does python Jinja2 engine
+ bool lstripBlocks = false;
+ //! Templates cache size
+ int cacheSize = 400;
+ //! If auto_reload is set to true (default) every time a template is requested the loader checks if the source changed and if yes, it will reload the template
+ bool autoReload = true;
+ //! Extensions set enabled for templates
+ Extensions extensions;
+ //! Controls Jinja2 compatibility mode
+ Jinja2CompatMode jinja2CompatMode = Jinja2CompatMode::None;
+ //! Default format for metadata block in the templates
+ 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
+ *
+ * This class is used for fine tuning of the templates behaviour and for state sharing between them. With this class
+ * it's possible to control template loading, provide template sources, set global variables, use template inheritance
+ * and inclusion.
+ *
+ * It's possible to load templates from the environment via \ref LoadTemplate or \ref LoadTemplateW methods
+ * or to pass instance of the environment directly to the \ref Template via constructor.
+ */
+class JINJA2CPP_EXPORT TemplateEnv
+{
+public:
+ using TimePoint = std::chrono::system_clock::time_point;
+ using TimeStamp = std::chrono::steady_clock::time_point;
+
+ /*!
+ * \brief Returns global settings for the environment
+ *
+ * @return Constant reference to the global settings
+ */
+ const Settings& GetSettings() const {return m_settings;}
+ /*!
+ * \brief Returns global settings for the environment available for modification
+ *
+ * @return Reference to the global settings
+ */
+ Settings& GetSettings() {return m_settings;}
+
+ /*!
+ * \brief Replace global settings for the environment with the new ones
+ *
+ * @param setts New settings
+ */
+ void SetSettings(const Settings& setts) {m_settings = setts;}
+
+ /*!
+ * \brief Add pointer to file system handler with the specified prefix
+ *
+ * Adds filesystem handler which provides access to the external source of templates. With added handlers it's
+ * possible to load templates from the `import`, `extends` and `include` jinja2 tags. Prefix is used for
+ * distinguish one templates source from another. \ref LoadTemplate or \ref LoadTemplateW methods use
+ * handlers to load templates with the specified name.
+ * Method is thread-unsafe. It's dangerous to add new filesystem handlers and load templates simultaneously.
+ *
+ * Basic usage:
+ * ```c++
+ * jinja2::TemplateEnv env;
+ *
+ * auto fs = std::make_shared<jinja2::MemoryFileSystem>();
+ * env.AddFilesystemHandler(std::string(), fs);
+ * fs->AddFile("base.j2tpl", "Hello World!");
+ * ```
+ *
+ * @param prefix Optional prefix of the handler's filesystem. Prefix is a part of the file name and passed to the handler's \ref IFilesystemHandler::OpenStream method
+ * @param h Shared pointer to the handler
+ */
+ void AddFilesystemHandler(std::string prefix, FilesystemHandlerPtr h)
+ {
+ m_filesystemHandlers.push_back(FsHandler{std::move(prefix), std::move(h)});
+ }
+ /*!
+ * \brief Add reference to file system handler with the specified prefix
+ *
+ * Adds filesystem handler which provides access to the external source of templates. With added handlers it's
+ * possible to load templates from the `import`, `extends` and `include` jinja2 tags. Prefix is used for
+ * distinguish one templates source from another. \ref LoadTemplate or \ref LoadTemplateW methods use
+ * handlers to load templates with the specified name.
+ * Method is thread-unsafe. It's dangerous to add new filesystem handlers and load templates simultaneously.
+ *
+ * Basic usage:
+ * ```c++
+ * jinja2::TemplateEnv env;
+ *
+ * MemoryFileSystem fs;
+ * env.AddFilesystemHandler(std::string(), fs);
+ * fs.AddFile("base.j2tpl", "Hello World!");
+ * ```
+ *
+ * @param prefix Optional prefix of the handler's filesystem. Prefix is a part of the file name and passed to the handler's \ref IFilesystemHandler::OpenStream method
+ * @param h Reference to the handler. It's assumed that lifetime of the handler is controlled externally
+ */
+ void AddFilesystemHandler(std::string prefix, IFilesystemHandler& h)
+ {
+ m_filesystemHandlers.push_back(FsHandler{std::move(prefix), std::shared_ptr<IFilesystemHandler>(&h, [](auto*) {})});
+ }
+ /*!
+ * \brief Load narrow char template with the specified name via registered file handlers
+ *
+ * In case of specified file present in any of the registered handlers, template is loaded and parsed. If any
+ * error occurred during the loading or parsing detailed diagnostic will be returned.
+ * Method is thread-unsafe. It's dangerous to add new filesystem handlers and load templates simultaneously.
+ *
+ * @param fileName Template name to load
+ *
+ * @return Either loaded template or load/parse error. See \ref ErrorInfoTpl
+ */
+ nonstd::expected<Template, ErrorInfo> LoadTemplate(std::string fileName);
+ /*!
+ * \brief Load wide char template with the specified name via registered file handlers
+ *
+ * In case of specified file present in any of the registered handlers, template is loaded and parsed. If any
+ * error occurred during the loading or parsing detailed diagnostic will be returned.
+ * Method is thread-unsafe. It's dangerous to add new filesystem handlers and load templates simultaneously.
+ *
+ * @param fileName Template name to load
+ *
+ * @return Either loaded template or load/parse error. See \ref ErrorInfoTpl
+ */
+ nonstd::expected<TemplateW, ErrorInfoW> LoadTemplateW(std::string fileName);
+
+ /*!
+ * \brief Add global variable to the environment
+ *
+ * Adds global variable which can be referred in any template which is loaded within this environment object.
+ * Method is thread-safe.
+ *
+ * @param name Name of the variable
+ * @param val Value of the variable
+ */
+ void AddGlobal(std::string name, Value val)
+ {
+ std::unique_lock<std::shared_timed_mutex> l(m_guard);
+ m_globalValues[std::move(name)] = std::move(val);
+ }
+ /*!
+ * \brief Remove global variable from the environment
+ *
+ * Removes global variable from the environment.
+ * Method is thread-safe.
+ *
+ * @param name Name of the variable
+ */
+ void RemoveGlobal(const std::string& name)
+ {
+ std::unique_lock<std::shared_timed_mutex> l(m_guard);
+ m_globalValues.erase(name);
+ }
+
+ /*!
+ * \brief Call the specified function with the current set of global variables under the internal lock
+ *
+ * Main purpose of this method is to help external code to enumerate global variables thread-safely. Provided functional object is called under the
+ * internal lock with the current set of global variables as an argument.
+ *
+ * @tparam Fn Type of the functional object to call
+ * @param fn Functional object to call
+ */
+ template<typename Fn>
+ void ApplyGlobals(Fn&& fn)
+ {
+ std::shared_lock<std::shared_timed_mutex> l(m_guard);
+ 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<typename CharT, typename T, typename Cache>
+ auto LoadTemplateImpl(TemplateEnv* env, std::string fileName, const T& filesystemHandlers, Cache& cache);
+
+
+private:
+ struct FsHandler
+ {
+ 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
+ {
+ std::optional<TimePoint> 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<FsHandler> m_filesystemHandlers;
+ Settings m_settings;
+ ValuesMap m_globalValues;
+ std::shared_timed_mutex m_guard;
+ std::unordered_map<std::string, TemplateCacheEntry> m_templateCache;
+ std::unordered_map<std::string, TemplateWCacheEntry> m_templateWCache;
+};
+
+} // namespace jinja2
+
+#endif // JINJA2CPP_TEMPLATE_ENV_H
diff --git a/contrib/libs/jinja2cpp/include/jinja2cpp/utils/i_comparable.h b/contrib/libs/jinja2cpp/include/jinja2cpp/utils/i_comparable.h
new file mode 100644
index 0000000000..76f58d5c88
--- /dev/null
+++ b/contrib/libs/jinja2cpp/include/jinja2cpp/utils/i_comparable.h
@@ -0,0 +1,16 @@
+#ifndef JINJA2CPP_ICOMPARABLE_H
+#define JINJA2CPP_ICOMPARABLE_H
+
+#include <jinja2cpp/config.h>
+
+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/contrib/libs/jinja2cpp/include/jinja2cpp/value.h b/contrib/libs/jinja2cpp/include/jinja2cpp/value.h
new file mode 100644
index 0000000000..545294fcd5
--- /dev/null
+++ b/contrib/libs/jinja2cpp/include/jinja2cpp/value.h
@@ -0,0 +1,763 @@
+#ifndef JINJA2CPP_VALUE_H
+#define JINJA2CPP_VALUE_H
+
+#include <jinja2cpp/generic_list.h>
+#include <jinja2cpp/utils/i_comparable.h>
+#include <jinja2cpp/value_ptr.h>
+
+#include <variant>
+#include <optional>
+#include <string_view>
+
+#include <atomic>
+#include <vector>
+#include <unordered_map>
+#include <string>
+#include <functional>
+#include <type_traits>
+#include <utility>
+
+namespace jinja2
+{
+//! Empty value container
+struct EmptyValue
+{
+ template<typename T>
+ 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 IMapItemAccessor : IComparable
+{
+ //! Destructor
+ 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;
+
+ /*!
+ * \brief Method is called to check presence of the item in the dictionary
+ *
+ * @param name Name of the item
+ *
+ * @return true if item is present and false otherwise.
+ */
+ virtual bool HasValue(const std::string& name) const = 0;
+ /*!
+ * \brief Method is called for retrieving the value by specified name
+ *
+ * @param name Name of the value to retrieve
+ *
+ * @return Requestd value or empty \ref Value if item is absent
+ */
+ virtual Value GetValueByName(const std::string& name) const = 0;
+ /*!
+ * \brief Method is called for retrieving collection of keys in the dictionary
+ *
+ * @return Collection of keys if any. Ordering of keys is unspecified.
+ */
+ virtual std::vector<std::string> 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 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 IMapItemAccessor interface instance
+ * and uses it to access particular items in the dictionaries.
+ */
+class GenericMap
+{
+public:
+ //! Default constructor
+ GenericMap() = default;
+
+ /*!
+ * \brief Initializing constructor
+ *
+ * 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 IMapItemAccessor interface
+ */
+ explicit GenericMap(std::function<const IMapItemAccessor* ()> accessor)
+ : m_accessor(std::move(accessor))
+ {
+ }
+
+ /*!
+ * \brief Check the presence the specific item in the dictionary
+ *
+ * @param name Name of the the item
+ *
+ * @return true of item is present and false otherwise
+ */
+ bool HasValue(const std::string& name) const
+ {
+ return m_accessor ? m_accessor()->HasValue(name) : false;
+ }
+
+ /*!
+ * \brief Get specific item from the dictionary
+ *
+ * @param name Name of the item to get
+ *
+ * @return Value of the item or empty \ref Value if no item
+ */
+ Value GetValueByName(const std::string& name) const;
+ /*!
+ * \brief Get size of the dictionary
+ *
+ * @return Size of the dictionary
+ */
+ size_t GetSize() const
+ {
+ return m_accessor ? m_accessor()->GetSize() : 0;
+ }
+ /*!
+ * \brief Get collection of keys from the dictionary
+ *
+ * @return Collection of the keys or empty collection if no keys
+ */
+ auto GetKeys() const
+ {
+ return m_accessor ? m_accessor()->GetKeys() : std::vector<std::string>();
+ }
+ /*!
+ * \brief Get the underlying access interface to the dictionary
+ *
+ * @return Pointer to the underlying interface or nullptr if no
+ */
+ auto GetAccessor() const
+ {
+ return m_accessor();
+ }
+
+ auto operator[](const std::string& name) const;
+
+private:
+ std::function<const IMapItemAccessor* ()> m_accessor;
+};
+
+bool operator==(const GenericMap& lhs, const GenericMap& rhs);
+bool operator!=(const GenericMap& lhs, const GenericMap& rhs);
+
+using ValuesList = std::vector<Value>;
+struct ValuesMap;
+struct UserCallableArgs;
+struct ParamInfo;
+struct UserCallable;
+
+template<typename T>
+using RecWrapper = types::ValuePtr<T>;
+
+/*!
+ * \brief Generic value class
+ *
+ * Variant-based class which is used for passing values to and from Jinja2C++ template engine. This class store the
+ * following types of values:
+ *
+ * - EmptyValue. In this case instance of this class threated as 'empty'
+ * - Boolean value.
+ * - String value.
+ * - Wide string value
+ * - String view value (std::string_view)
+ * - Wide string view value (std::wstring_view)
+ * - integer (int64_t) value
+ * - floating point (double) value
+ * - Simple list of other values (\ref ValuesList)
+ * - Simple map of other values (\ref ValuesMap)
+ * - Generic list of other values (\ref GenericList)
+ * - Generic map of other values (\ref GenericMap)
+ * - User-defined callable (\ref UserCallable)
+ *
+ * Exact value can be accessed via std::visit method applied to the result of the Value::data() call or any of
+ * asXXX method (ex. \ref Value::asString). In case of string retrieval it's better to use \ref AsString or \ref
+ * AsWString functions. Thay hide all nececcary transformations between various types of strings (or string views).
+ */
+class Value
+{
+public:
+ using ValueData = std::variant<
+ EmptyValue,
+ bool,
+ std::string,
+ std::wstring,
+ std::string_view,
+ std::wstring_view,
+ int64_t,
+ double,
+ RecWrapper<ValuesList>,
+ RecWrapper<ValuesMap>,
+ GenericList,
+ GenericMap,
+ RecWrapper<UserCallable>
+ >;
+
+ template<typename T, typename ... L>
+ struct AnyOf : public std::false_type {};
+
+ template<typename T, typename H, typename ... L>
+ struct AnyOf<T, H, L...> : public std::integral_constant<bool, std::is_same<std::decay_t<T>, H>::value || AnyOf<T, L...>::value> {};
+
+ //! Default constructor
+ Value();
+ //! Copy constructor
+ Value(const Value& val);
+ //! Move constructor
+ Value(Value&& val) noexcept;
+ //! Desctructor
+ // ~Value();
+ ~Value();
+
+ //! Assignment operator
+ Value& operator=(const Value&);
+ //! Move assignment operator
+ Value& operator=(Value&&) noexcept;
+ /*!
+ * \brief Generic initializing constructor
+ *
+ * Creates \ref Value from the arbitrary type which is compatible with types listed in \ref Value::ValueData
+ *
+ * @tparam T Type of value to create \ref Value instance from
+ * @param val Value which should be used to initialize \ref Value instance
+ */
+ template<typename T>
+ Value(T&& val, typename std::enable_if<!AnyOf<T, Value, ValuesList, ValuesMap, UserCallable>::value>::type* = nullptr)
+ : m_data(std::forward<T>(val))
+ {
+ }
+ /*!
+ * \brief Initializing constructor from pointer to the null-terminated narrow string
+ *
+ * @param val Null-terminated string which should be used to initialize \ref Value instance
+ */
+ Value(const char* val)
+ : m_data(std::string(val))
+ {
+ }
+ /*!
+ * \brief Initializing constructor from pointer to the null-terminated wide string
+ *
+ * @param val Null-terminated string which should be used to initialize \ref Value instance
+ */
+ Value(const wchar_t* val)
+ : m_data(std::wstring(val))
+ {
+ }
+ /*!
+ * \brief Initializing constructor from the narrow string literal
+ *
+ * @param val String literal which should be used to initialize \ref Value instance
+ */
+ template<size_t N>
+ Value(char (&val)[N])
+ : m_data(std::string(val))
+ {
+ }
+ /*!
+ * \brief Initializing constructor from the wide string literal
+ *
+ * @param val String literal which should be used to initialize \ref Value instance
+ */
+ template<size_t N>
+ Value(wchar_t (&val)[N])
+ : m_data(std::wstring(val))
+ {
+ }
+ /*!
+ * \brief Initializing constructor from the int value
+ *
+ * @param val Integer value which should be used to initialize \ref Value instance
+ */
+ Value(int val)
+ : m_data(static_cast<int64_t>(val))
+ {
+ }
+ /*!
+ * \brief Initializing constructor from the \ref ValuesList
+ *
+ * @param list List of values which should be used to initialize \ref Value instance
+ */
+ Value(const ValuesList& list);
+ /*!
+ * \brief Initializing constructor from the \ref ValuesMap
+ *
+ * @param map Map of values which should be used to initialize \ref Value instance
+ */
+ Value(const ValuesMap& map);
+ /*!
+ * \brief Initializing constructor from the \ref UserCallable
+ *
+ * @param callable UserCallable which should be used to initialize \ref Value instance
+ */
+ Value(const UserCallable& callable);
+ /*!
+ * \brief Initializing move constructor from the \ref ValuesList
+ *
+ * @param list List of values which should be used to initialize \ref Value instance
+ */
+ Value(ValuesList&& list) noexcept;
+ /*!
+ * \brief Initializing move constructor from the \ref ValuesMap
+ *
+ * @param map Map of values which should be used to initialize \ref Value instance
+ */
+ Value(ValuesMap&& map) noexcept;
+ /*!
+ * \brief Initializing move constructor from the \ref UserCallable
+ *
+ * @param callable UserCallable which should be used to initialize \ref Value instance
+ */
+ Value(UserCallable&& callable);
+
+ /*!
+ * \brief Get the non-mutable stored data object
+ *
+ * Returns the stored data object in order to get the typed value from it. For instance:
+ * ```c++
+ * inline std::string AsString(const jinja2::Value& val)
+ * {
+ * return std::visit(StringGetter(), val.data());
+ * }
+ * ```
+ *
+ * @return Non-mutable stored data object
+ */
+ const ValueData& data() const {return m_data;}
+ /*!
+ * \brief Get the mutable stored data object
+ *
+ * Returns the stored data object in order to get the typed value from it. For instance:
+ * ```c++
+ * inline std::string AsString(Value& val)
+ * {
+ * return std::visit(StringGetter(), val.data());
+ * }
+ * ```
+ *
+ * @return Mutable stored data object
+ */
+ ValueData& data() {return m_data;}
+
+ //! Test Value for containing std::string object
+ bool isString() const
+ {
+ return std::get_if<std::string>(&m_data) != nullptr;
+ }
+ /*!
+ * \brief Returns mutable containing std::string object
+ *
+ * Returns containing std::string object. Appropriate exception is thrown in case non-string containing value
+ *
+ * @return Mutable containing std::string object
+ */
+ auto& asString()
+ {
+ return std::get<std::string>(m_data);
+ }
+ /*!
+ * \brief Returns non-mutable containing std::string object
+ *
+ * Returns containing std::string object. Appropriate exception is thrown in case of non-string containing value
+ *
+ * @return Non-mutable containing std::string object
+ */
+ auto& asString() const
+ {
+ return std::get<std::string>(m_data);
+ }
+
+ //! Test Value for containing std::wstring object
+ bool isWString() const
+ {
+ return std::get_if<std::wstring>(&m_data) != nullptr;
+ }
+ /*!
+ * \brief Returns mutable containing std::wstring object
+ *
+ * Returns containing std::wstring object. Appropriate exception is thrown in case of non-wstring containing value
+ *
+ * @return Mutable containing std::wstring object
+ */
+ auto& asWString()
+ {
+ return std::get<std::wstring>(m_data);
+ }
+ /*!
+ * \brief Returns non-mutable containing std::wstring object
+ *
+ * Returns containing std::wstring object. Appropriate exception is thrown in case of non-wstring containing value
+ *
+ * @return Non-mutable containing std::wstring object
+ */
+ auto& asWString() const
+ {
+ return std::get<std::wstring>(m_data);
+ }
+
+ //! Test Value for containing jinja2::ValuesList object
+ bool isList() const
+ {
+ return std::get_if<RecWrapper<ValuesList>>(&m_data) != nullptr || std::get_if<GenericList>(&m_data) != nullptr;
+ }
+ /*!
+ * \brief Returns mutable containing jinja2::ValuesList object
+ *
+ * Returns containing jinja2::ValuesList object. Appropriate exception is thrown in case of non-Valueslist containing value
+ *
+ * @return Mutable containing jinja2::ValuesList object
+ */
+ auto& asList()
+ {
+ return *std::get<RecWrapper<ValuesList>>(m_data);
+ }
+ /*!
+ * \brief Returns non-mutable containing jinja2::ValuesList object
+ *
+ * Returns containing jinja2::ValuesList object. Appropriate exception is thrown in case of non-Valueslist containing value
+ *
+ * @return Non-mutable containing jinja2::ValuesList object
+ */
+ auto& asList() const
+ {
+ return *std::get<RecWrapper<ValuesList>>(m_data);
+ }
+ //! Test Value for containing jinja2::ValuesMap object
+ bool isMap() const
+ {
+ return std::get_if<RecWrapper<ValuesMap>>(&m_data) != nullptr || std::get_if<GenericMap>(&m_data) != nullptr;
+ }
+ /*!
+ * \brief Returns mutable containing jinja2::ValuesMap object
+ *
+ * Returns containing jinja2::ValuesMap object. Appropriate exception is thrown in case of non-ValuesMap containing value
+ *
+ * @return Mutable containing jinja2::ValuesMap object
+ */
+ auto& asMap()
+ {
+ return *std::get<RecWrapper<ValuesMap>>(m_data);
+ }
+ /*!
+ * \brief Returns non-mutable containing jinja2::ValuesMap object
+ *
+ * Returns containing jinja2::ValuesMap object. Appropriate exception is thrown in case of non-ValuesMap containing value
+ *
+ * @return Non-mutable containing jinja2::ValuesMap object
+ */
+ auto& asMap() const
+ {
+ return *std::get<RecWrapper<ValuesMap>>(m_data);
+ }
+
+ template<typename T>
+ auto get()
+ {
+ return std::get<T>(m_data);
+ }
+
+ template<typename T>
+ auto get() const
+ {
+ return std::get<T>(m_data);
+ }
+
+ template<typename T>
+ auto getPtr()
+ {
+ return std::get_if<T>(&m_data); // m_data.index() == ValueData::template index_of<T>() ? &m_data.get<T>() : nullptr;
+ }
+
+ template<typename T>
+ auto getPtr() const
+ {
+ return std::get_if<T>(&m_data); // m_data.index() == ValueData::template index_of<T>() ? &m_data.get<T>() : nullptr;
+ }
+
+ //! Test Value for emptyness
+ bool isEmpty() const
+ {
+ return std::get_if<EmptyValue>(&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<Value>& lhs, const types::ValuePtr<Value>& rhs);
+bool operator!=(const types::ValuePtr<Value>& lhs, const types::ValuePtr<Value>& rhs);
+bool operator==(const types::ValuePtr<std::vector<Value>>& lhs, const types::ValuePtr<std::vector<Value>>& rhs);
+bool operator!=(const types::ValuePtr<std::vector<Value>>& lhs, const types::ValuePtr<std::vector<Value>>& rhs);
+
+struct ValuesMap : std::unordered_map<std::string, Value>
+{
+ using unordered_map::unordered_map;
+};
+
+bool operator==(const types::ValuePtr<ValuesMap>& lhs, const types::ValuePtr<ValuesMap>& rhs);
+bool operator!=(const types::ValuePtr<ValuesMap>& lhs, const types::ValuePtr<ValuesMap>& rhs);
+
+/*!
+ * \brief Information about user-callable parameters passed from Jinja2 call context
+ *
+ * This structure prepared by the Jinja2C++ engine and filled by information about call parameters gathered from the
+ * call context. See documentation for \ref UserCallable for detailed information
+ *
+ */
+struct UserCallableParams
+{
+ //! Values of parameters mapped according to \ref UserCallable::argsInfo
+ ValuesMap args;
+ //! Values of extra positional args got from the call expression
+ Value extraPosArgs;
+ //! Values of extra named args got from the call expression
+ Value extraKwArgs;
+ //! Context object which provides access to the current variables set of the template
+ Value context;
+ bool paramsParsed = false;
+
+ Value operator[](const std::string& paramName) const
+ {
+ auto p = args.find(paramName);
+ if (p == args.end())
+ return Value();
+
+ return p->second;
+ }
+};
+
+/*!
+ * \brief Information about one argument of the user-defined callable
+ *
+ * This structure is used as a description of the user-callable argument. Information from this structure is used
+ * by the Jinja2C++ engine to map actual call parameters to the expected ones by the user-defined callable.
+ */
+struct ArgInfo
+{
+ //! Name of the argument
+ std::string paramName;
+ //! Mandatory flag
+ bool isMandatory;
+ //! Default value for the argument
+ Value defValue;
+
+ ArgInfo(std::string name, bool isMandat = false, Value defVal = Value())
+ : paramName(std::move(name))
+ , isMandatory(isMandat)
+ , 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<typename T>
+struct ArgInfoT : public ArgInfo
+{
+ using type = T;
+
+ using ArgInfo::ArgInfo;
+ ArgInfoT(const ArgInfo& info)
+ : ArgInfo(info)
+ {
+ }
+ ArgInfoT(ArgInfo&& info) noexcept
+ : ArgInfo(std::move(info))
+ {
+ }
+};
+
+/*!
+ * \brief User-callable descriptor
+ *
+ * This descriptor is used for description of the user-defined callables passed to the Jinja2C++ engine. Information
+ * from this descriptor is used by the engine to properly parse and prepare of the call parameters and pass it to the
+ * user-callable. For instance, such kind of user-defined callable passed as a parameter:
+ * ```c++
+ * jinja2::UserCallable uc;
+ * uc.callable = [](auto& params)->jinja2::Value {
+ * auto str1 = params["str1"];
+ * auto str2 = params["str2"];
+ *
+ * if (str1.isString())
+ * return str1.asString() + " " + str2.asString();
+ *
+ * return str1.asWString() + L" " + str2.asWString();
+ * };
+ * uc.argsInfo = {{"str1", true}, {"str2", true}};
+ * params["test"] = std::move(uc);
+ * ```
+ * This declaration defines user-defined callable which takes two named parameters: `str1` and `str2`. Further, it's
+ * possible to call this user-defined callable from the Jinja2 template this way:
+ * ```jinja2
+ * {{ test('Hello', 'World!') }}
+ * ```
+ * or:
+ * ```
+ * {{ test(str2='World!', str1='Hello') }}
+ * ```
+ * Jinja2C++ engine maps actual call parameters according the information from \ref UserCallable::argsInfo field and
+ * pass them as a \ref UserCallableParams structure. Every named param (explicitly defined in the call or it's default value)
+ * passed throught \ref UserCallableParams::args field. Every extra positional param mentoined in call passed as \ref
+ * UserCallableParams::extraPosArgs. Every extra named param mentoined in call passed as \ref
+ * UserCallableParams::extraKwArgs.
+ *
+ * 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 JINJA2CPP_EXPORT UserCallable
+{
+ using UserCallableFunctionPtr = std::function<Value (const UserCallableParams&)>;
+ UserCallable() : m_counter(++m_gen) {}
+ UserCallable(const UserCallableFunctionPtr& fptr, const std::vector<ArgInfo>& 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
+ UserCallableFunctionPtr callable;
+ //! Information about arguments of the user-defined callable
+ std::vector<ArgInfo> 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<UserCallable>& lhs, const types::ValuePtr<UserCallable>& rhs);
+bool operator!=(const types::ValuePtr<UserCallable>& lhs, const types::ValuePtr<UserCallable>& rhs);
+
+
+inline Value::Value(const UserCallable& callable)
+ : m_data(types::MakeValuePtr<UserCallable>(callable))
+{
+}
+
+inline Value::Value(UserCallable&& callable)
+ : m_data(types::MakeValuePtr<UserCallable>(std::move(callable)))
+{
+}
+
+inline Value GenericMap::GetValueByName(const std::string& name) const
+{
+ return m_accessor ? m_accessor()->GetValueByName(name) : Value();
+}
+inline auto GenericMap::operator[](const std::string& name) const
+{
+ return GetValueByName(name);
+}
+
+inline Value::Value() = default;
+inline Value::Value(const Value& val) = default;
+inline Value::Value(Value&& val) noexcept
+ : m_data(std::move(val.m_data))
+{
+}
+inline Value::~Value() = default;
+inline Value& Value::operator=(const Value&) = default;
+inline Value& Value::operator=(Value&& val) noexcept
+{
+ if (this == &val)
+ return *this;
+
+ m_data.swap(val.m_data);
+ return *this;
+}
+inline Value::Value(const ValuesMap& map)
+ : m_data(types::MakeValuePtr<ValuesMap>(map))
+{
+}
+inline Value::Value(const ValuesList& list)
+ : m_data(types::MakeValuePtr<ValuesList>(list))
+{
+}
+inline Value::Value(ValuesList&& list) noexcept
+ : m_data(types::MakeValuePtr<ValuesList>(std::move(list)))
+{
+}
+inline Value::Value(ValuesMap&& map) noexcept
+ : m_data(types::MakeValuePtr<ValuesMap>(std::move(map)))
+{
+}
+
+} // namespace jinja2
+
+#endif // JINJA2CPP_VALUE_H
diff --git a/contrib/libs/jinja2cpp/include/jinja2cpp/value_ptr.h b/contrib/libs/jinja2cpp/include/jinja2cpp/value_ptr.h
new file mode 100644
index 0000000000..26eee6b6bb
--- /dev/null
+++ b/contrib/libs/jinja2cpp/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<typename T>
+using ValuePtr = polymorphic_value<T>;
+
+template<class T, class... Ts>
+ValuePtr<T> MakeValuePtr(Ts&&... ts)
+{
+ return make_polymorphic_value<T>(std::forward<Ts&&>(ts)...);
+}
+
+template<class T, class U, class... Ts>
+ValuePtr<T> MakeValuePtr(Ts&&... ts)
+{
+ return make_polymorphic_value<T, U>(std::forward<Ts>(ts)...);
+}
+
+} // namespace types
+} // namespace jinja2
+
+#endif // JINJA2CPP_VALUE_PTR_H
diff --git a/contrib/libs/jinja2cpp/src/ast_visitor.h b/contrib/libs/jinja2cpp/src/ast_visitor.h
new file mode 100644
index 0000000000..01b519829b
--- /dev/null
+++ b/contrib/libs/jinja2cpp/src/ast_visitor.h
@@ -0,0 +1,117 @@
+#ifndef JINJA2CPP_SRC_AST_VISITOR_H
+#define JINJA2CPP_SRC_AST_VISITOR_H
+
+namespace jinja2
+{
+class IRendererBase;
+class ExpressionEvaluatorBase;
+class Statement;
+class ForStatement;
+class IfStatement;
+class ElseBranchStatement;
+class SetStatement;
+class ParentBlockStatement;
+class BlockStatement;
+class ExtendsStatement;
+class IncludeStatement;
+class ImportStatement;
+class MacroStatement;
+class MacroCallStatement;
+class ComposedRenderer;
+class RawTextRenderer;
+class ExpressionRenderer;
+
+class StatementVisitor;
+
+class VisitableStatement
+{
+public:
+ virtual ~VisitableStatement() = default;
+
+ virtual void ApplyVisitor(StatementVisitor* visitor) = 0;
+ virtual void ApplyVisitor(StatementVisitor* visitor) const = 0;
+};
+
+#define VISITABLE_STATEMENT() \
+ void ApplyVisitor(StatementVisitor* visitor) override {visitor->DoVisit(this);} \
+ void ApplyVisitor(StatementVisitor* visitor) const override {visitor->DoVisit(this);} \
+
+namespace detail
+{
+template<typename Base, typename Type>
+class VisitorIfaceImpl : public Base
+{
+public:
+ using Base::DoVisit;
+
+ virtual void DoVisit(Type*) {}
+ virtual void DoVisit(const Type*) {}
+};
+
+template<typename Type>
+class VisitorIfaceImpl<void, Type>
+{
+public:
+ virtual void DoVisit(Type*) {}
+ virtual void DoVisit(const Type*) {}
+};
+
+template<typename Base, typename ... Types>
+struct VisitorBaseImpl;
+
+template<typename Base, typename T, typename ... Types>
+struct VisitorBaseImpl<Base, T, Types...>
+{
+ using current_base = VisitorIfaceImpl<Base, T>;
+ using base_type = typename VisitorBaseImpl<current_base, Types...>::base_type;
+};
+
+template<typename Base, typename T>
+struct VisitorBaseImpl<Base, T>
+{
+ using base_type = VisitorIfaceImpl<Base, T>;
+};
+
+
+template<typename ... Types>
+struct VisitorBase
+{
+ using type = typename VisitorBaseImpl<void, Types ...>::base_type;
+};
+} // namespace detail
+
+template<typename ... Types>
+using VisitorBase = typename detail::VisitorBase<Types...>::type;
+
+class StatementVisitor : public VisitorBase<
+ IRendererBase,
+ Statement,
+ ForStatement,
+ IfStatement,
+ ElseBranchStatement,
+ SetStatement,
+ ParentBlockStatement,
+ BlockStatement,
+ ExtendsStatement,
+ IncludeStatement,
+ ImportStatement,
+ MacroStatement,
+ MacroCallStatement,
+ ComposedRenderer,
+ RawTextRenderer,
+ ExpressionRenderer>
+{
+public:
+ void Visit(VisitableStatement* stmt)
+ {
+ stmt->ApplyVisitor(this);
+ }
+ void Visit(const VisitableStatement* stmt)
+ {
+ stmt->ApplyVisitor(this);
+ }
+};
+} // namespace jinja2
+
+
+#endif
diff --git a/contrib/libs/jinja2cpp/src/binding/rapid_json_serializer.cpp b/contrib/libs/jinja2cpp/src/binding/rapid_json_serializer.cpp
new file mode 100644
index 0000000000..1234759d27
--- /dev/null
+++ b/contrib/libs/jinja2cpp/src/binding/rapid_json_serializer.cpp
@@ -0,0 +1,127 @@
+#include "rapid_json_serializer.h"
+
+#include "../value_visitors.h"
+
+#include <rapidjson/prettywriter.h>
+
+namespace jinja2
+{
+namespace rapidjson_serializer
+{
+namespace
+{
+struct JsonInserter : visitors::BaseVisitor<rapidjson::Value>
+{
+ using BaseVisitor::operator();
+
+ explicit JsonInserter(rapidjson::Document::AllocatorType& allocator)
+ : m_allocator(allocator)
+ {
+ }
+
+ rapidjson::Value operator()(const ListAdapter& list) const
+ {
+ rapidjson::Value listValue(rapidjson::kArrayType);
+
+ for (auto& v : list)
+ {
+ listValue.PushBack(Apply<JsonInserter>(v, m_allocator), m_allocator);
+ }
+ return listValue;
+ }
+
+ rapidjson::Value operator()(const MapAdapter& map) const
+ {
+ rapidjson::Value mapNode(rapidjson::kObjectType);
+
+ const auto& keys = map.GetKeys();
+ for (auto& k : keys)
+ {
+ mapNode.AddMember(rapidjson::Value(k.c_str(), m_allocator), Apply<JsonInserter>(map.GetValueByName(k), m_allocator), m_allocator);
+ }
+
+ return mapNode;
+ }
+
+ rapidjson::Value operator()(const KeyValuePair& kwPair) const
+ {
+ rapidjson::Value pairNode(rapidjson::kObjectType);
+ pairNode.AddMember(rapidjson::Value(kwPair.key.c_str(), m_allocator), Apply<JsonInserter>(kwPair.value, m_allocator), m_allocator);
+
+ return pairNode;
+ }
+
+ rapidjson::Value operator()(const std::string& str) const { return rapidjson::Value(str.c_str(), m_allocator); }
+
+ rapidjson::Value operator()(const std::string_view& str) const
+ {
+ return rapidjson::Value(str.data(), static_cast<rapidjson::SizeType>(str.size()), m_allocator);
+ }
+
+ rapidjson::Value operator()(const std::wstring& str) const
+ {
+ auto s = ConvertString<std::string>(str);
+ return rapidjson::Value(s.c_str(), m_allocator);
+ }
+
+ rapidjson::Value operator()(const std::wstring_view& str) const
+ {
+ auto s = ConvertString<std::string>(str);
+ return rapidjson::Value(s.c_str(), m_allocator);
+ }
+
+ rapidjson::Value operator()(bool val) const { return rapidjson::Value(val); }
+
+ rapidjson::Value operator()(EmptyValue) const { return rapidjson::Value(); }
+
+ rapidjson::Value operator()(const Callable&) const { return rapidjson::Value("<callable>"); }
+
+ rapidjson::Value operator()(double val) const { return rapidjson::Value(val); }
+
+ rapidjson::Value operator()(int64_t val) const { return rapidjson::Value(val); }
+
+ rapidjson::Document::AllocatorType& m_allocator;
+};
+} // namespace
+
+DocumentWrapper::DocumentWrapper()
+ : m_document(std::make_shared<rapidjson::Document>())
+{
+}
+
+ValueWrapper DocumentWrapper::CreateValue(const InternalValue& value) const
+{
+ auto v = Apply<JsonInserter>(value, m_document->GetAllocator());
+ return ValueWrapper(std::move(v), m_document);
+}
+
+ValueWrapper::ValueWrapper(rapidjson::Value&& value, std::shared_ptr<rapidjson::Document> document)
+ : m_value(std::move(value))
+ , m_document(document)
+{
+}
+
+std::string ValueWrapper::AsString(const uint8_t indent) const
+{
+ using Writer = rapidjson::Writer<rapidjson::StringBuffer, rapidjson::Document::EncodingType, rapidjson::UTF8<>>;
+ using PrettyWriter = rapidjson::PrettyWriter<rapidjson::StringBuffer, rapidjson::Document::EncodingType, rapidjson::UTF8<>>;
+
+ rapidjson::StringBuffer buffer;
+ if (indent == 0)
+ {
+ Writer writer(buffer);
+ m_value.Accept(writer);
+ }
+ else
+ {
+ PrettyWriter writer(buffer);
+ writer.SetIndent(' ', indent);
+ writer.SetFormatOptions(rapidjson::kFormatSingleLineArray);
+ m_value.Accept(writer);
+ }
+
+ return buffer.GetString();
+}
+
+} // namespace rapidjson_serializer
+} // namespace jinja2
diff --git a/contrib/libs/jinja2cpp/src/binding/rapid_json_serializer.h b/contrib/libs/jinja2cpp/src/binding/rapid_json_serializer.h
new file mode 100644
index 0000000000..66e26c14da
--- /dev/null
+++ b/contrib/libs/jinja2cpp/src/binding/rapid_json_serializer.h
@@ -0,0 +1,50 @@
+#ifndef JINJA2CPP_SRC_RAPID_JSON_SERIALIZER_H
+#define JINJA2CPP_SRC_RAPID_JSON_SERIALIZER_H
+
+#include "../internal_value.h"
+
+#include <rapidjson/document.h>
+#include <rapidjson/rapidjson.h>
+
+#include <memory>
+
+namespace jinja2
+{
+namespace rapidjson_serializer
+{
+
+class ValueWrapper
+{
+ friend class DocumentWrapper;
+
+public:
+ ValueWrapper(ValueWrapper&&) = default;
+ ValueWrapper& operator=(ValueWrapper&&) = default;
+
+ std::string AsString(uint8_t indent = 0) const;
+
+private:
+ ValueWrapper(rapidjson::Value&& value, std::shared_ptr<rapidjson::Document> document);
+
+ rapidjson::Value m_value;
+ std::shared_ptr<rapidjson::Document> m_document;
+};
+
+class DocumentWrapper
+{
+public:
+ DocumentWrapper();
+
+ DocumentWrapper(DocumentWrapper&&) = default;
+ DocumentWrapper& operator=(DocumentWrapper&&) = default;
+
+ ValueWrapper CreateValue(const InternalValue& value) const;
+
+private:
+ std::shared_ptr<rapidjson::Document> m_document;
+};
+
+} // namespace rapidjson_serializer
+} // namespace jinja2
+
+#endif // JINJA2CPP_SRC_RAPID_JSON_SERIALIZER_H
diff --git a/contrib/libs/jinja2cpp/src/error_handling.h b/contrib/libs/jinja2cpp/src/error_handling.h
new file mode 100644
index 0000000000..db0f02ce52
--- /dev/null
+++ b/contrib/libs/jinja2cpp/src/error_handling.h
@@ -0,0 +1,63 @@
+#ifndef JINJA2CPP_SRC_ERROR_HANDLING_H
+#define JINJA2CPP_SRC_ERROR_HANDLING_H
+
+#include "lexer.h"
+#include <jinja2cpp/error_info.h>
+#include <contrib/restricted/expected-lite/include/nonstd/expected.hpp>
+
+#include <initializer_list>
+#include <vector>
+
+namespace jinja2
+{
+
+struct ParseError
+{
+ ParseError() = default;
+ ParseError(ErrorCode code, Token tok)
+ : errorCode(code)
+ , errorToken(tok)
+ {}
+
+ ParseError(ErrorCode code, Token tok, std::initializer_list<Token> toks)
+ : errorCode(code)
+ , errorToken(tok)
+ , relatedTokens(toks)
+ {}
+ ParseError(const ParseError&) = default;
+ ParseError(ParseError&& other) noexcept(true)
+ : errorCode(std::move(other.errorCode))
+ , errorToken(std::move(other.errorToken))
+ , relatedTokens(std::move(other.relatedTokens))
+ {}
+
+ ParseError& operator =(const ParseError&) = default;
+ ParseError& operator =(ParseError&& error) noexcept
+ {
+ if (this == &error)
+ return *this;
+
+ std::swap(errorCode, error.errorCode);
+ std::swap(errorToken, error.errorToken);
+ std::swap(relatedTokens, error.relatedTokens);
+
+ return *this;
+ }
+
+ ErrorCode errorCode;
+ Token errorToken;
+ std::vector<Token> relatedTokens;
+};
+
+inline auto MakeParseError(ErrorCode code, Token tok)
+{
+ return nonstd::make_unexpected(ParseError{code, tok});
+}
+
+inline auto MakeParseError(ErrorCode code, Token tok, std::initializer_list<Token> toks)
+{
+ return nonstd::make_unexpected(ParseError{code, tok, toks});
+}
+
+} // namespace jinja2
+#endif // JINJA2CPP_SRC_ERROR_HANDLING_H
diff --git a/contrib/libs/jinja2cpp/src/error_info.cpp b/contrib/libs/jinja2cpp/src/error_info.cpp
new file mode 100644
index 0000000000..25ff5dfee7
--- /dev/null
+++ b/contrib/libs/jinja2cpp/src/error_info.cpp
@@ -0,0 +1,289 @@
+#include "helpers.h"
+
+#include <fmt/format.h>
+#include <fmt/xchar.h>
+#include <jinja2cpp/error_info.h>
+#include <iterator>
+
+namespace
+{
+template<typename FmtCtx>
+struct ValueRenderer
+{
+ using CharT = typename FmtCtx::char_type;
+ FmtCtx* ctx;
+
+ explicit ValueRenderer(FmtCtx* c)
+ : ctx(c)
+ {
+ }
+
+ constexpr void operator()(bool val) const {
+ fmt::format_to(
+ ctx->out(),
+ UNIVERSAL_STR("{}").GetValue<CharT>(),
+ (val ? UNIVERSAL_STR("True").GetValue<CharT>(): UNIVERSAL_STR("False").GetValue<CharT>()));
+ }
+ void operator()(const jinja2::EmptyValue&) const { fmt::format_to(ctx->out(), UNIVERSAL_STR("").GetValue<CharT>()); }
+ template<typename CharU>
+ void operator()(const std::basic_string<CharU>& val) const
+ {
+ fmt::format_to(ctx->out(), UNIVERSAL_STR("{}").GetValue<CharT>(), jinja2::ConvertString<std::basic_string<CharT>>(val));
+ }
+
+ template<typename CharU>
+ void operator()(const std::basic_string_view<CharU>& val) const
+ {
+ fmt::format_to(ctx->out(), UNIVERSAL_STR("{}").GetValue<CharT>(), jinja2::ConvertString<std::basic_string<CharT>>(val));
+ }
+
+ void operator()(const jinja2::ValuesList& vals) const
+ {
+ fmt::format_to(ctx->out(), UNIVERSAL_STR("{{").GetValue<CharT>());
+ bool isFirst = true;
+ for (auto& val : vals)
+ {
+ if (isFirst)
+ isFirst = false;
+ else
+ fmt::format_to(ctx->out(), UNIVERSAL_STR(", ").GetValue<CharT>());
+ std::visit(ValueRenderer<FmtCtx>(ctx), val.data());
+ }
+ fmt::format_to(ctx->out(), UNIVERSAL_STR("}}").GetValue<CharT>());
+ }
+
+ void operator()(const jinja2::ValuesMap& vals) const
+ {
+ fmt::format_to(ctx->out(), UNIVERSAL_STR("{{").GetValue<CharT>());
+ bool isFirst = true;
+ for (auto& val : vals)
+ {
+ if (isFirst)
+ isFirst = false;
+ else
+ fmt::format_to(ctx->out(), UNIVERSAL_STR(", ").GetValue<CharT>());
+
+ fmt::format_to(ctx->out(), UNIVERSAL_STR("{{\"{}\",").GetValue<CharT>(), jinja2::ConvertString<std::basic_string<CharT>>(val.first));
+ std::visit(ValueRenderer<FmtCtx>(ctx), val.second.data());
+ fmt::format_to(ctx->out(), UNIVERSAL_STR("}}").GetValue<CharT>());
+ }
+ fmt::format_to(ctx->out(), UNIVERSAL_STR("}}").GetValue<CharT>());
+ }
+
+ template<typename T>
+ void operator()(const jinja2::RecWrapper<T>& val) const
+ {
+ this->operator()(const_cast<const T&>(*val));
+ }
+
+ void operator()(const jinja2::GenericMap& /*val*/) const {}
+
+ void operator()(const jinja2::GenericList& /*val*/) const {}
+
+ void operator()(const jinja2::UserCallable& /*val*/) const {}
+
+ template<typename T>
+ void operator()(const T& val) const
+ {
+ fmt::format_to(ctx->out(), UNIVERSAL_STR("{}").GetValue<CharT>(), val);
+ }
+};
+} // namespace
+
+namespace fmt
+{
+template<typename CharT>
+struct formatter<jinja2::Value, CharT>
+{
+ template<typename ParseContext>
+ constexpr auto parse(ParseContext& ctx)
+ {
+ return ctx.begin();
+ }
+
+ template<typename FormatContext>
+ auto format(const jinja2::Value& val, FormatContext& ctx)
+ {
+ std::visit(ValueRenderer<FormatContext>(&ctx), val.data());
+ return fmt::format_to(ctx.out(), UNIVERSAL_STR("").GetValue<CharT>());
+ }
+};
+} // namespace fmt
+
+namespace jinja2
+{
+
+template<typename CharT>
+void RenderErrorInfo(std::basic_string<CharT>& result, const ErrorInfoTpl<CharT>& errInfo)
+{
+ using string_t = std::basic_string<CharT>;
+ auto out = fmt::basic_memory_buffer<CharT>();
+
+ auto& loc = errInfo.GetErrorLocation();
+
+ fmt::format_to(std::back_inserter(out), UNIVERSAL_STR("{}:{}:{}: error: ").GetValue<CharT>(), ConvertString<string_t>(loc.fileName), loc.line, loc.col);
+ ErrorCode errCode = errInfo.GetCode();
+ switch (errCode)
+ {
+ case ErrorCode::Unspecified:
+ format_to(std::back_inserter(out), UNIVERSAL_STR("Parse error").GetValue<CharT>());
+ break;
+ case ErrorCode::UnexpectedException:
+ {
+ auto& extraParams = errInfo.GetExtraParams();
+ format_to(std::back_inserter(out), UNIVERSAL_STR("Unexpected exception occurred during template processing. Exception: {}").GetValue<CharT>(), extraParams[0]);
+ break;
+ }
+ case ErrorCode::MetadataParseError:
+ {
+ auto& extraParams = errInfo.GetExtraParams();
+ format_to(std::back_inserter(out), UNIVERSAL_STR("Error occurred during template metadata parsing. Error: {}").GetValue<CharT>(), extraParams[0]);
+ break;
+ }
+ case ErrorCode::YetUnsupported:
+ format_to(std::back_inserter(out), UNIVERSAL_STR("This feature has not been supported yet").GetValue<CharT>());
+ break;
+ case ErrorCode::FileNotFound:
+ format_to(std::back_inserter(out), UNIVERSAL_STR("File not found").GetValue<CharT>());
+ break;
+ case ErrorCode::ExpectedStringLiteral:
+ format_to(std::back_inserter(out), UNIVERSAL_STR("String expected").GetValue<CharT>());
+ break;
+ case ErrorCode::ExpectedIdentifier:
+ format_to(std::back_inserter(out), UNIVERSAL_STR("Identifier expected").GetValue<CharT>());
+ break;
+ case ErrorCode::ExpectedSquareBracket:
+ format_to(std::back_inserter(out), UNIVERSAL_STR("']' expected").GetValue<CharT>());
+ break;
+ case ErrorCode::ExpectedRoundBracket:
+ format_to(std::back_inserter(out), UNIVERSAL_STR("')' expected").GetValue<CharT>());
+ break;
+ case ErrorCode::ExpectedCurlyBracket:
+ format_to(std::back_inserter(out), UNIVERSAL_STR("'}}' expected").GetValue<CharT>());
+ break;
+ case ErrorCode::ExpectedToken:
+ {
+ auto& extraParams = errInfo.GetExtraParams();
+ format_to(std::back_inserter(out), UNIVERSAL_STR("Unexpected token '{}'").GetValue<CharT>(), extraParams[0]);
+ if (extraParams.size() > 1)
+ {
+ format_to(std::back_inserter(out), UNIVERSAL_STR(". Expected: ").GetValue<CharT>());
+ for (std::size_t i = 1; i < extraParams.size(); ++ i)
+ {
+ if (i != 1)
+ format_to(std::back_inserter(out), UNIVERSAL_STR(", ").GetValue<CharT>());
+ format_to(std::back_inserter(out), UNIVERSAL_STR("\'{}\'").GetValue<CharT>(), extraParams[i]);
+ }
+ }
+ break;
+ }
+ case ErrorCode::ExpectedExpression:
+ {
+ auto& extraParams = errInfo.GetExtraParams();
+ format_to(std::back_inserter(out), UNIVERSAL_STR("Expected expression, got: '{}'").GetValue<CharT>(), extraParams[0]);
+ break;
+ }
+ case ErrorCode::ExpectedEndOfStatement:
+ {
+ auto& extraParams = errInfo.GetExtraParams();
+ format_to(std::back_inserter(out), UNIVERSAL_STR("Expected end of statement, got: '{}'").GetValue<CharT>(), extraParams[0]);
+ break;
+ }
+ case ErrorCode::ExpectedRawEnd:
+ format_to(std::back_inserter(out), UNIVERSAL_STR("Expected end of raw block").GetValue<CharT>());
+ break;
+ case ErrorCode::ExpectedMetaEnd:
+ format_to(std::back_inserter(out), UNIVERSAL_STR("Expected end of meta block").GetValue<CharT>());
+ break;
+ case ErrorCode::UnexpectedToken:
+ {
+ auto& extraParams = errInfo.GetExtraParams();
+ format_to(std::back_inserter(out), UNIVERSAL_STR("Unexpected token: '{}'").GetValue<CharT>(), extraParams[0]);
+ break;
+ }
+ case ErrorCode::UnexpectedStatement:
+ {
+ auto& extraParams = errInfo.GetExtraParams();
+ format_to(std::back_inserter(out), UNIVERSAL_STR("Unexpected statement: '{}'").GetValue<CharT>(), extraParams[0]);
+ break;
+ }
+ case ErrorCode::UnexpectedCommentBegin:
+ format_to(std::back_inserter(out), UNIVERSAL_STR("Unexpected comment begin").GetValue<CharT>());
+ break;
+ case ErrorCode::UnexpectedCommentEnd:
+ format_to(std::back_inserter(out), UNIVERSAL_STR("Unexpected comment end").GetValue<CharT>());
+ break;
+ case ErrorCode::UnexpectedRawBegin:
+ format_to(std::back_inserter(out), UNIVERSAL_STR("Unexpected raw block begin").GetValue<CharT>());
+ break;
+ case ErrorCode::UnexpectedRawEnd:
+ format_to(std::back_inserter(out), UNIVERSAL_STR("Unexpected raw block end").GetValue<CharT>());
+ break;
+ case ErrorCode::UnexpectedMetaBegin:
+ format_to(std::back_inserter(out), UNIVERSAL_STR("Unexpected meta block begin").GetValue<CharT>());
+ break;
+ case ErrorCode::UnexpectedMetaEnd:
+ format_to(std::back_inserter(out), UNIVERSAL_STR("Unexpected meta block end").GetValue<CharT>());
+ break;
+ case ErrorCode::UnexpectedExprBegin:
+ format_to(std::back_inserter(out), UNIVERSAL_STR("Unexpected expression block begin").GetValue<CharT>());
+ break;
+ case ErrorCode::UnexpectedExprEnd:
+ format_to(std::back_inserter(out), UNIVERSAL_STR("Unexpected expression block end").GetValue<CharT>());
+ break;
+ case ErrorCode::UnexpectedStmtBegin:
+ format_to(std::back_inserter(out), UNIVERSAL_STR("Unexpected statement block begin").GetValue<CharT>());
+ break;
+ case ErrorCode::UnexpectedStmtEnd:
+ format_to(std::back_inserter(out), UNIVERSAL_STR("Unexpected statement block end").GetValue<CharT>());
+ break;
+ case ErrorCode::TemplateNotParsed:
+ format_to(std::back_inserter(out), UNIVERSAL_STR("Template not parsed").GetValue<CharT>());
+ break;
+ case ErrorCode::TemplateNotFound:
+ format_to(std::back_inserter(out), UNIVERSAL_STR("Template(s) not found: {}").GetValue<CharT>(), errInfo.GetExtraParams()[0]);
+ break;
+ case ErrorCode::InvalidTemplateName:
+ format_to(std::back_inserter(out), UNIVERSAL_STR("Invalid template name: {}").GetValue<CharT>(), errInfo.GetExtraParams()[0]);
+ break;
+ case ErrorCode::InvalidValueType:
+ format_to(std::back_inserter(out), UNIVERSAL_STR("Invalid value type").GetValue<CharT>());
+ break;
+ case ErrorCode::ExtensionDisabled:
+ format_to(std::back_inserter(out), UNIVERSAL_STR("Extension disabled").GetValue<CharT>());
+ break;
+ case ErrorCode::TemplateEnvAbsent:
+ format_to(std::back_inserter(out), UNIVERSAL_STR("Template environment doesn't set").GetValue<CharT>());
+ break;
+ }
+ format_to(std::back_inserter(out), UNIVERSAL_STR("\n{}").GetValue<CharT>(), errInfo.GetLocationDescr());
+ result = to_string(out);
+}
+
+template<>
+std::string ErrorInfoTpl<char>::ToString() const
+{
+ std::string result;
+ RenderErrorInfo(result, *this);
+ return result;
+}
+
+template<>
+std::wstring ErrorInfoTpl<wchar_t>::ToString() const
+{
+ std::wstring result;
+ RenderErrorInfo(result, *this);
+ return result;
+}
+
+std::ostream& operator << (std::ostream& os, const ErrorInfo& res)
+{
+ os << res.ToString();
+ return os;
+}
+std::wostream& operator << (std::wostream& os, const ErrorInfoW& res)
+{
+ os << res.ToString();
+ return os;
+}
+} // namespace jinja2
diff --git a/contrib/libs/jinja2cpp/src/expression_evaluator.cpp b/contrib/libs/jinja2cpp/src/expression_evaluator.cpp
new file mode 100644
index 0000000000..136e33d8fc
--- /dev/null
+++ b/contrib/libs/jinja2cpp/src/expression_evaluator.cpp
@@ -0,0 +1,602 @@
+#include "expression_evaluator.h"
+#include "filters.h"
+#include "generic_adapters.h"
+#include "internal_value.h"
+#include "out_stream.h"
+#include "testers.h"
+#include "value_visitors.h"
+
+#include <boost/algorithm/string/join.hpp>
+#include <boost/container/small_vector.hpp>
+
+#include <cmath>
+#include <stack>
+
+namespace jinja2
+{
+
+void ExpressionEvaluatorBase::Render(OutStream& stream, RenderContext& values)
+{
+ auto val = Evaluate(values);
+ stream.WriteValue(val);
+}
+
+
+InternalValue FullExpressionEvaluator::Evaluate(RenderContext& values)
+{
+ if (!m_expression)
+ return InternalValue();
+
+ auto result = m_expression->Evaluate(values);
+
+ if (m_tester && !m_tester->Evaluate(values))
+ return m_tester->EvaluateAltValue(values);
+
+ return result;
+}
+
+void FullExpressionEvaluator::Render(OutStream& stream, RenderContext& values)
+{
+ if (!m_tester)
+ m_expression->Render(stream, values);
+ else
+ Expression::Render(stream, values);
+}
+
+InternalValue ValueRefExpression::Evaluate(RenderContext& values)
+{
+ bool found = false;
+ auto p = values.FindValue(m_valueName, found);
+ if (found)
+ return p->second;
+
+ return InternalValue();
+}
+
+InternalValue SubscriptExpression::Evaluate(RenderContext& values)
+{
+ InternalValue cur = m_value->Evaluate(values);
+
+ for (auto idx : m_subscriptExprs)
+ {
+ auto subscript = idx->Evaluate(values);
+ auto newVal = Subscript(cur, subscript, &values);
+ if (cur.ShouldExtendLifetime())
+ newVal.SetParentData(cur);
+ std::swap(newVal, cur);
+ }
+
+ return cur;
+}
+
+InternalValue FilteredExpression::Evaluate(RenderContext& values)
+{
+ auto origResult = m_expression->Evaluate(values);
+ return m_filter->Evaluate(origResult, values);
+}
+
+InternalValue UnaryExpression::Evaluate(RenderContext& values)
+{
+ return Apply<visitors::UnaryOperation>(m_expr->Evaluate(values), m_oper);
+}
+
+BinaryExpression::BinaryExpression(BinaryExpression::Operation oper, ExpressionEvaluatorPtr<> leftExpr, ExpressionEvaluatorPtr<> rightExpr)
+ : m_oper(oper)
+ , m_leftExpr(leftExpr)
+ , m_rightExpr(rightExpr)
+{
+ if (m_oper == In)
+ {
+ CallParamsInfo params;
+ params.kwParams["seq"] = rightExpr;
+ m_inTester = CreateTester("in", params);
+ }
+}
+
+InternalValue BinaryExpression::Evaluate(RenderContext& context)
+{
+ InternalValue leftVal = m_leftExpr->Evaluate(context);
+ InternalValue rightVal = m_oper == In ? InternalValue() : m_rightExpr->Evaluate(context);
+ InternalValue result;
+
+ switch (m_oper)
+ {
+ case jinja2::BinaryExpression::LogicalAnd:
+ {
+ bool left = ConvertToBool(leftVal);
+ if (left)
+ left = ConvertToBool(rightVal);
+ result = static_cast<bool>(left);
+ break;
+ }
+ case jinja2::BinaryExpression::LogicalOr:
+ {
+ bool left = ConvertToBool(leftVal);
+ if (!left)
+ left = ConvertToBool(rightVal);
+ result = static_cast<bool>(left);
+ break;
+ }
+ case jinja2::BinaryExpression::LogicalEq:
+ case jinja2::BinaryExpression::LogicalNe:
+ case jinja2::BinaryExpression::LogicalGt:
+ case jinja2::BinaryExpression::LogicalLt:
+ case jinja2::BinaryExpression::LogicalGe:
+ case jinja2::BinaryExpression::LogicalLe:
+ case jinja2::BinaryExpression::Plus:
+ case jinja2::BinaryExpression::Minus:
+ case jinja2::BinaryExpression::Mul:
+ case jinja2::BinaryExpression::Div:
+ case jinja2::BinaryExpression::DivReminder:
+ case jinja2::BinaryExpression::DivInteger:
+ case jinja2::BinaryExpression::Pow:
+ result = Apply2<visitors::BinaryMathOperation>(leftVal, rightVal, m_oper);
+ break;
+ case jinja2::BinaryExpression::In:
+ {
+ result = m_inTester->Test(leftVal, context);
+ break;
+ }
+ case jinja2::BinaryExpression::StringConcat:
+ {
+ auto leftStr = context.GetRendererCallback()->GetAsTargetString(leftVal);
+ auto rightStr = context.GetRendererCallback()->GetAsTargetString(rightVal);
+ TargetString resultStr;
+ std::string* nleftStr = GetIf<std::string>(&leftStr);
+ if (nleftStr != nullptr)
+ {
+ auto* nrightStr = GetIf<std::string>(&rightStr);
+ resultStr = *nleftStr + *nrightStr;
+ }
+ else
+ {
+ auto* wleftStr = GetIf<std::wstring>(&leftStr);
+ auto* wrightStr = GetIf<std::wstring>(&rightStr);
+ resultStr = *wleftStr + *wrightStr;
+ }
+ result = InternalValue(std::move(resultStr));
+ break;
+ }
+ default:
+ break;
+ }
+ return result;
+}
+
+InternalValue TupleCreator::Evaluate(RenderContext& context)
+{
+ InternalValueList result;
+ for (auto& e : m_exprs)
+ {
+ result.push_back(e->Evaluate(context));
+ }
+
+ return ListAdapter::CreateAdapter(std::move(result));
+}
+
+InternalValue DictCreator::Evaluate(RenderContext& context)
+{
+ InternalValueMap result;
+ for (auto& e : m_exprs)
+ {
+ result[e.first] = e.second->Evaluate(context);
+ }
+
+ return CreateMapAdapter(std::move(result));;
+}
+
+ExpressionFilter::ExpressionFilter(const std::string& filterName, CallParamsInfo params)
+{
+ m_filter = CreateFilter(filterName, std::move(params));
+ if (!m_filter)
+ throw std::runtime_error("Can't find filter '" + filterName + "'");
+}
+
+InternalValue ExpressionFilter::Evaluate(const InternalValue& baseVal, RenderContext& context)
+{
+ if (m_parentFilter)
+ return m_filter->Filter(m_parentFilter->Evaluate(baseVal, context), context);
+
+ return m_filter->Filter(baseVal, context);
+}
+
+IsExpression::IsExpression(ExpressionEvaluatorPtr<> value, const std::string& tester, CallParamsInfo params)
+ : m_value(value)
+{
+ m_tester = CreateTester(tester, std::move(params));
+ if (!m_tester)
+ throw std::runtime_error("Can't find tester '" + tester + "'");
+}
+
+InternalValue IsExpression::Evaluate(RenderContext& context)
+{
+ return m_tester->Test(m_value->Evaluate(context), context);
+}
+
+bool IfExpression::Evaluate(RenderContext& context)
+{
+ return ConvertToBool(m_testExpr->Evaluate(context));
+}
+
+InternalValue IfExpression::EvaluateAltValue(RenderContext& context)
+{
+ return m_altValue ? m_altValue->Evaluate(context) : InternalValue();
+}
+
+/*
+InternalValue DictionaryCreator::Evaluate(RenderContext& context)
+{
+ ValuesMap result;
+ for (auto& i : m_items)
+ {
+ result[i.first] = i.second->Evaluate(context);
+ }
+
+ return result;
+}*/
+
+InternalValue CallExpression::Evaluate(RenderContext& values)
+{
+ auto fn = m_valueRef->Evaluate(values);
+
+ auto fnId = ConvertToInt(fn, InvalidFn);
+
+
+ switch (fnId)
+ {
+ case RangeFn:
+ return CallGlobalRange(values);
+ case LoopCycleFn:
+ return CallLoopCycle(values);
+ default:
+ return CallArbitraryFn(values);
+ }
+}
+
+void CallExpression::Render(OutStream& stream, RenderContext& values)
+{
+ auto fnVal = m_valueRef->Evaluate(values);
+ const Callable* callable = GetIf<Callable>(&fnVal);
+ if (callable == nullptr)
+ {
+ fnVal = Subscript(fnVal, std::string("operator()"), &values);
+ callable = GetIf<Callable>(&fnVal);
+ if (callable == nullptr)
+ {
+ Expression::Render(stream, values);
+ return;
+ }
+ }
+
+ auto callParams = helpers::EvaluateCallParams(m_params, values);
+
+ if (callable->GetType() == Callable::Type::Expression)
+ {
+ stream.WriteValue(callable->GetExpressionCallable()(callParams, values));
+ }
+ else
+ {
+ callable->GetStatementCallable()(callParams, stream, values);
+ }
+}
+
+InternalValue CallExpression::CallArbitraryFn(RenderContext& values)
+{
+ auto fnVal = m_valueRef->Evaluate(values);
+ Callable* callable = GetIf<Callable>(&fnVal);
+ if (callable == nullptr)
+ {
+ fnVal = Subscript(fnVal, std::string("operator()"), nullptr);
+ callable = GetIf<Callable>(&fnVal);
+ if (callable == nullptr)
+ return InternalValue();
+ }
+
+ auto kind = callable->GetKind();
+ if (kind != Callable::GlobalFunc && kind != Callable::UserCallable && kind != Callable::Macro)
+ return InternalValue();
+
+ auto callParams = helpers::EvaluateCallParams(m_params, values);
+
+ if (callable->GetType() == Callable::Type::Expression)
+ {
+ return callable->GetExpressionCallable()(callParams, values);
+ }
+
+ TargetString resultStr;
+ auto stream = values.GetRendererCallback()->GetStreamOnString(resultStr);
+ callable->GetStatementCallable()(callParams, stream, values);
+ return resultStr;
+}
+
+InternalValue CallExpression::CallGlobalRange(RenderContext& values)
+{
+ bool isArgsParsed = true;
+
+ auto args = helpers::ParseCallParamsInfo({ { "start" }, { "stop", true }, { "step" } }, m_params, isArgsParsed);
+ if (!isArgsParsed)
+ return InternalValue();
+
+
+ auto startExpr = args["start"];
+ auto stopExpr = args["stop"];
+ auto stepExpr = args["step"];
+
+ InternalValue startVal = startExpr ? startExpr->Evaluate(values) : InternalValue();
+ InternalValue stopVal = stopExpr ? stopExpr->Evaluate(values) : InternalValue();
+ InternalValue stepVal = stepExpr ? stepExpr->Evaluate(values) : InternalValue();
+
+ int64_t start = Apply<visitors::IntegerEvaluator>(startVal);
+ int64_t stop = Apply<visitors::IntegerEvaluator>(stopVal);
+ int64_t step = Apply<visitors::IntegerEvaluator>(stepVal);
+
+ if (!stepExpr)
+ {
+ step = 1;
+ }
+ else
+ {
+ if (step == 0)
+ return InternalValue();
+ }
+
+ auto distance = stop - start;
+ auto items_count = distance / step;
+ items_count = items_count < 0 ? 0 : static_cast<size_t>(items_count);
+
+ return ListAdapter::CreateAdapter(static_cast<size_t>(items_count),
+ [start, step](size_t idx) { return InternalValue(static_cast<int64_t>(start + step * idx)); });
+}
+
+InternalValue CallExpression::CallLoopCycle(RenderContext& values)
+{
+ bool loopFound = false;
+ auto loopValP = values.FindValue("loop", loopFound);
+ if (!loopFound)
+ return InternalValue();
+
+ auto loop = GetIf<MapAdapter>(&loopValP->second);
+ int64_t baseIdx = Apply<visitors::IntegerEvaluator>(loop->GetValueByName("index0"));
+ auto idx = static_cast<size_t>(baseIdx % m_params.posParams.size());
+ return m_params.posParams[idx]->Evaluate(values);
+}
+
+
+void SetupGlobals(InternalValueMap& globalParams)
+{
+ globalParams["range"] = InternalValue(static_cast<int64_t>(RangeFn));
+ // globalParams["loop"] = MapAdapter::CreateAdapter(InternalValueMap{{"cycle", InternalValue(static_cast<int64_t>(LoopCycleFn))}});
+}
+
+namespace helpers
+{
+enum ArgState
+{
+ NotFound,
+ NotFoundMandatory,
+ Keyword,
+ Positional,
+ Ignored
+};
+
+enum ParamState
+{
+ UnknownPos,
+ UnknownKw,
+ MappedPos,
+ MappedKw,
+};
+
+template<typename Result>
+struct ParsedArgumentDefaultValGetter;
+
+template<>
+struct ParsedArgumentDefaultValGetter<ParsedArguments>
+{
+ static auto Get(const InternalValue& val) { return val; }
+};
+
+template<>
+struct ParsedArgumentDefaultValGetter<ParsedArgumentsInfo>
+{
+ static auto Get(const InternalValue& val) { return std::make_shared<ConstantExpression>(val); }
+};
+
+template<typename Result, typename T, typename P>
+Result ParseCallParamsImpl(const T& args, const P& params, bool& isSucceeded)
+{
+ struct ArgInfo
+ {
+ ArgState state = NotFound;
+ int prevNotFound = -1;
+ int nextNotFound = -1;
+ const ArgumentInfo* info = nullptr;
+ };
+
+ boost::container::small_vector<ArgInfo, 8> argsInfo(args.size());
+ boost::container::small_vector<ParamState, 8> posParamsInfo(params.posParams.size());
+
+ isSucceeded = true;
+
+ Result result;
+
+ int argIdx = 0;
+ 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)
+ {
+ argsInfo[argIdx].info = &argInfo;
+
+ if (argInfo.name == "*args" || argInfo.name=="**kwargs")
+ {
+ argsInfo[argIdx ++].state = Ignored;
+ continue;
+ }
+
+ auto p = params.kwParams.find(argInfo.name);
+ if (p != params.kwParams.end())
+ {
+ result.args[argInfo.name] = p->second;
+ argsInfo[argIdx].state = Keyword;
+ ++foundKwArgs;
+ }
+ else
+ {
+ if (argInfo.mandatory)
+ {
+ argsInfo[argIdx].state = NotFoundMandatory;
+ if (firstMandatoryIdx == -1)
+ firstMandatoryIdx = argIdx;
+ }
+ else
+ {
+ argsInfo[argIdx].state = NotFound;
+ }
+
+
+ if (prevNotFound != -1)
+ argsInfo[prevNotFound].nextNotFound = argIdx;
+
+ argsInfo[argIdx].prevNotFound = prevNotFound;
+ prevNotFound = argIdx;
+ }
+
+
+ ++ argIdx;
+ }
+
+ std::size_t startPosArg = firstMandatoryIdx == -1 ? 0 : firstMandatoryIdx;
+ std::size_t curPosArg = startPosArg;
+ std::size_t eatenPosArgs = 0;
+
+ // Determine the range for positional arguments scanning
+ bool isFirstTime = true;
+ for (; eatenPosArgs < posParamsInfo.size() && startPosArg < args.size(); eatenPosArgs = eatenPosArgs + (argsInfo[startPosArg].state == Ignored ? 0 : 1))
+ {
+ if (isFirstTime)
+ {
+ for (; startPosArg < args.size() && (argsInfo[startPosArg].state == Keyword || argsInfo[startPosArg].state == Positional); ++ startPosArg)
+ ;
+
+ isFirstTime = false;
+ if (startPosArg == args.size())
+ break;
+ continue;
+ }
+
+ prevNotFound = argsInfo[startPosArg].prevNotFound;
+ if (prevNotFound != -1)
+ {
+ startPosArg = static_cast<std::size_t>(prevNotFound);
+ }
+ else if (curPosArg == args.size())
+ {
+ break;
+ }
+ else
+ {
+ int nextPosArg = argsInfo[curPosArg].nextNotFound;
+ if (nextPosArg == -1)
+ break;
+ curPosArg = static_cast<std::size_t>(nextPosArg);
+ }
+ }
+
+ // Map positional params to the desired arguments
+ auto curArg = static_cast<int>(startPosArg);
+ for (std::size_t idx = 0; idx < eatenPosArgs && curArg != -1 && static_cast<size_t>(curArg) < argsInfo.size(); ++ idx, curArg = argsInfo[curArg].nextNotFound)
+ {
+ if (argsInfo[curArg].state == Ignored)
+ continue;
+
+ result.args[argsInfo[curArg].info->name] = params.posParams[idx];
+ argsInfo[curArg].state = Positional;
+ }
+
+ // Fill default arguments (if missing) and check for mandatory
+ for (std::size_t idx = 0; idx < argsInfo.size(); ++ idx)
+ {
+ auto& argInfo = argsInfo[idx];
+ switch (argInfo.state)
+ {
+ case Positional:
+ case Keyword:
+ case Ignored:
+ continue;
+ case NotFound:
+ {
+ if (!IsEmpty(argInfo.info->defaultVal))
+ {
+#if __cplusplus >= 201703L
+ if constexpr (std::is_same<Result, ParsedArgumentsInfo>::value)
+ result.args[argInfo.info->name] = std::make_shared<ConstantExpression>(argInfo.info->defaultVal);
+ else
+ result.args[argInfo.info->name] = argInfo.info->defaultVal;
+#else
+ result.args[argInfo.info->name] = ParsedArgumentDefaultValGetter<Result>::Get(argInfo.info->defaultVal);
+#endif
+ }
+ break;
+ }
+ case NotFoundMandatory:
+ isSucceeded = false;
+ break;
+ }
+ }
+
+ // Fill the extra positional and kw-args
+ for (auto& kw : params.kwParams)
+ {
+ if (result.args.find(kw.first) != result.args.end())
+ continue;
+
+ result.extraKwArgs[kw.first] = kw.second;
+ }
+
+ for (auto idx = eatenPosArgs; idx < params.posParams.size(); ++ idx)
+ result.extraPosArgs.push_back(params.posParams[idx]);
+
+
+ return result;
+}
+
+ParsedArguments ParseCallParams(const std::initializer_list<ArgumentInfo>& args, const CallParams& params, bool& isSucceeded)
+{
+ return ParseCallParamsImpl<ParsedArguments>(args, params, isSucceeded);
+}
+
+ParsedArguments ParseCallParams(const std::vector<ArgumentInfo>& args, const CallParams& params, bool& isSucceeded)
+{
+ return ParseCallParamsImpl<ParsedArguments>(args, params, isSucceeded);
+}
+
+ParsedArgumentsInfo ParseCallParamsInfo(const std::initializer_list<ArgumentInfo>& args, const CallParamsInfo& params, bool& isSucceeded)
+{
+ return ParseCallParamsImpl<ParsedArgumentsInfo>(args, params, isSucceeded);
+}
+
+ParsedArgumentsInfo ParseCallParamsInfo(const std::vector<ArgumentInfo>& args, const CallParamsInfo& params, bool& isSucceeded)
+{
+ return ParseCallParamsImpl<ParsedArgumentsInfo>(args, params, isSucceeded);
+}
+
+CallParams EvaluateCallParams(const CallParamsInfo& info, RenderContext& context)
+{
+ CallParams result;
+
+ for (auto& p : info.posParams)
+ result.posParams.push_back(p->Evaluate(context));
+
+ for (auto& kw : info.kwParams)
+ result.kwParams[kw.first] = kw.second->Evaluate(context);
+
+ return result;
+}
+
+} // namespace helpers
+} // namespace jinja2
diff --git a/contrib/libs/jinja2cpp/src/expression_evaluator.h b/contrib/libs/jinja2cpp/src/expression_evaluator.h
new file mode 100644
index 0000000000..06b1c40f60
--- /dev/null
+++ b/contrib/libs/jinja2cpp/src/expression_evaluator.h
@@ -0,0 +1,619 @@
+#ifndef JINJA2CPP_SRC_EXPRESSION_EVALUATOR_H
+#define JINJA2CPP_SRC_EXPRESSION_EVALUATOR_H
+
+#include "internal_value.h"
+#include "render_context.h"
+
+#include <jinja2cpp/utils/i_comparable.h>
+
+#include <memory>
+#include <limits>
+
+namespace jinja2
+{
+
+enum
+{
+ InvalidFn = -1,
+ RangeFn = 1,
+ LoopCycleFn = 2
+};
+
+class ExpressionEvaluatorBase : public IComparable
+{
+public:
+ virtual ~ExpressionEvaluatorBase() {}
+
+ virtual InternalValue Evaluate(RenderContext& values) = 0;
+ virtual void Render(OutStream& stream, RenderContext& values);
+};
+
+template<typename T = ExpressionEvaluatorBase>
+using ExpressionEvaluatorPtr = std::shared_ptr<T>;
+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<std::string, InternalValue> kwParams;
+ std::vector<InternalValue> 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<std::string, ExpressionEvaluatorPtr<>> kwParams;
+ std::vector<ExpressionEvaluatorPtr<>> 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 = false;
+ InternalValue defaultVal;
+
+ ArgumentInfo(std::string argName, bool isMandatory = false, InternalValue def = InternalValue())
+ : name(std::move(argName))
+ , mandatory(isMandatory)
+ , defaultVal(std::move(def))
+ {
+ }
+};
+
+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<std::string, ExpressionEvaluatorPtr<>> args;
+ std::unordered_map<std::string, ExpressionEvaluatorPtr<>> extraKwArgs;
+ std::vector<ExpressionEvaluatorPtr<>> extraPosArgs;
+
+ ExpressionEvaluatorPtr<> operator[](const std::string& name) const
+ {
+ auto p = args.find(name);
+ if (p == args.end())
+ return ExpressionEvaluatorPtr<>();
+
+ return p->second;
+ }
+};
+
+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<std::string, InternalValue> args;
+ std::unordered_map<std::string, InternalValue> extraKwArgs;
+ std::vector<InternalValue> extraPosArgs;
+
+ InternalValue operator[](const std::string& name) const
+ {
+ auto p = args.find(name);
+ if (p == args.end())
+ return InternalValue();
+
+ return p->second;
+ }
+};
+
+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;
+
+class FullExpressionEvaluator : public ExpressionEvaluatorBase
+{
+public:
+ void SetExpression(ExpressionEvaluatorPtr<Expression> expr)
+ {
+ m_expression = std::move(expr);
+ }
+ void SetTester(ExpressionEvaluatorPtr<IfExpression> expr)
+ {
+ m_tester = std::move(expr);
+ }
+ InternalValue Evaluate(RenderContext& values) override;
+ void Render(OutStream &stream, RenderContext &values) override;
+
+ bool IsEqual(const IComparable& other) const override
+ {
+ const auto* eval = dynamic_cast<const FullExpressionEvaluator*>(&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<Expression> m_expression;
+ ExpressionEvaluatorPtr<IfExpression> m_tester;
+};
+
+class ValueRefExpression : public Expression
+{
+public:
+ ValueRefExpression(std::string valueName)
+ : m_valueName(std::move(valueName))
+ {
+ }
+ InternalValue Evaluate(RenderContext& values) override;
+
+ bool IsEqual(const IComparable& other) const override
+ {
+ auto* value = dynamic_cast<const ValueRefExpression*>(&other);
+ if (!value)
+ return false;
+ return m_valueName != value->m_valueName;
+ }
+private:
+ std::string m_valueName;
+};
+
+class SubscriptExpression : public Expression
+{
+public:
+ SubscriptExpression(ExpressionEvaluatorPtr<Expression> value)
+ : m_value(value)
+ {
+ }
+ InternalValue Evaluate(RenderContext& values) override;
+ void AddIndex(ExpressionEvaluatorPtr<Expression> value)
+ {
+ m_subscriptExprs.push_back(value);
+ }
+
+ bool IsEqual(const IComparable& other) const override
+ {
+ auto* otherPtr = dynamic_cast<const SubscriptExpression*>(&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<Expression> m_value;
+ std::vector<ExpressionEvaluatorPtr<Expression>> m_subscriptExprs;
+};
+
+class FilteredExpression : public Expression
+{
+public:
+ explicit FilteredExpression(ExpressionEvaluatorPtr<Expression> expression, ExpressionEvaluatorPtr<ExpressionFilter> filter)
+ : m_expression(std::move(expression))
+ , m_filter(std::move(filter))
+ {
+ }
+ InternalValue Evaluate(RenderContext&) override;
+ bool IsEqual(const IComparable& other) const override
+ {
+ auto* otherPtr = dynamic_cast<const FilteredExpression*>(&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<Expression> m_expression;
+ ExpressionEvaluatorPtr<ExpressionFilter> m_filter;
+};
+
+class ConstantExpression : public Expression
+{
+public:
+ ConstantExpression(InternalValue constant)
+ : m_constant(constant)
+ {}
+ InternalValue Evaluate(RenderContext&) override
+ {
+ return m_constant;
+ }
+
+ bool IsEqual(const IComparable& other) const override
+ {
+ auto* otherVal = dynamic_cast<const ConstantExpression*>(&other);
+ if (!otherVal)
+ return false;
+ return m_constant == otherVal->m_constant;
+ }
+private:
+ InternalValue m_constant;
+};
+
+class TupleCreator : public Expression
+{
+public:
+ TupleCreator(std::vector<ExpressionEvaluatorPtr<>> exprs)
+ : m_exprs(std::move(exprs))
+ {
+ }
+
+ InternalValue Evaluate(RenderContext&) override;
+
+ bool IsEqual(const IComparable& other) const override
+ {
+ auto* val = dynamic_cast<const TupleCreator*>(&other);
+ if (!val)
+ return false;
+ return m_exprs == val->m_exprs;
+ }
+private:
+ std::vector<ExpressionEvaluatorPtr<>> m_exprs;
+};
+/*
+class DictionaryCreator : public Expression
+{
+public:
+ DictionaryCreator(std::unordered_map<std::string, ExpressionEvaluatorPtr<>> items)
+ : m_items(std::move(items))
+ {
+ }
+
+ InternalValue Evaluate(RenderContext&) override;
+
+private:
+ std::unordered_map<std::string, ExpressionEvaluatorPtr<>> m_items;
+};*/
+
+class DictCreator : public Expression
+{
+public:
+ DictCreator(std::unordered_map<std::string, ExpressionEvaluatorPtr<>> exprs)
+ : m_exprs(std::move(exprs))
+ {
+ }
+
+ InternalValue Evaluate(RenderContext&) override;
+
+ bool IsEqual(const IComparable& other) const override
+ {
+ auto* val = dynamic_cast<const DictCreator*>(&other);
+ if (!val)
+ return false;
+ return m_exprs == val->m_exprs;
+ }
+private:
+ std::unordered_map<std::string, ExpressionEvaluatorPtr<>> m_exprs;
+};
+
+class UnaryExpression : public Expression
+{
+public:
+ enum Operation
+ {
+ LogicalNot,
+ UnaryPlus,
+ UnaryMinus
+ };
+
+ UnaryExpression(Operation oper, ExpressionEvaluatorPtr<> expr)
+ : m_oper(oper)
+ , m_expr(expr)
+ {}
+ InternalValue Evaluate(RenderContext&) override;
+
+ bool IsEqual(const IComparable& other) const override
+ {
+ auto* val = dynamic_cast<const UnaryExpression*>(&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;
+};
+
+class IsExpression : public Expression
+{
+public:
+ virtual ~IsExpression() {}
+
+ struct ITester : IComparable
+ {
+ virtual ~ITester() {}
+ virtual bool Test(const InternalValue& baseVal, RenderContext& context) = 0;
+ };
+ using TesterPtr = std::shared_ptr<ITester>;
+ using TesterFactoryFn = std::function<TesterPtr(CallParamsInfo params)>;
+
+ 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<const IsExpression*>(&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;
+ TesterPtr m_tester;
+};
+
+class BinaryExpression : public Expression
+{
+public:
+ enum Operation
+ {
+ LogicalAnd,
+ LogicalOr,
+ LogicalEq,
+ LogicalNe,
+ LogicalGt,
+ LogicalLt,
+ LogicalGe,
+ LogicalLe,
+ In,
+ Plus,
+ Minus,
+ Mul,
+ Div,
+ DivReminder,
+ DivInteger,
+ Pow,
+ StringConcat
+ };
+
+ enum CompareType
+ {
+ Undefined = 0,
+ CaseSensitive = 0,
+ CaseInsensitive = 1
+ };
+
+ BinaryExpression(Operation oper, ExpressionEvaluatorPtr<> leftExpr, ExpressionEvaluatorPtr<> rightExpr);
+ InternalValue Evaluate(RenderContext&) override;
+
+ bool IsEqual(const IComparable& other) const override
+ {
+ auto* val = dynamic_cast<const BinaryExpression*>(&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;
+ IsExpression::TesterPtr m_inTester;
+};
+
+
+class CallExpression : public Expression
+{
+public:
+ virtual ~CallExpression() {}
+
+ CallExpression(ExpressionEvaluatorPtr<> valueRef, CallParamsInfo params)
+ : m_valueRef(std::move(valueRef))
+ , m_params(std::move(params))
+ {
+ }
+
+ InternalValue Evaluate(RenderContext &values) override;
+ void Render(OutStream &stream, RenderContext &values) override;
+
+ auto& GetValueRef() const {return m_valueRef;}
+ auto& GetParams() const {return m_params;}
+
+ bool IsEqual(const IComparable& other) const override
+ {
+ auto* val = dynamic_cast<const CallExpression*>(&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);
+ InternalValue CallLoopCycle(RenderContext &values);
+
+private:
+ ExpressionEvaluatorPtr<> m_valueRef;
+ CallParamsInfo m_params;
+};
+
+class ExpressionFilter : public IComparable
+{
+public:
+ virtual ~ExpressionFilter() {}
+
+ struct IExpressionFilter : IComparable
+ {
+ virtual ~IExpressionFilter() {}
+ virtual InternalValue Filter(const InternalValue& baseVal, RenderContext& context) = 0;
+ };
+ using ExpressionFilterPtr = std::shared_ptr<IExpressionFilter>;
+ using FilterFactoryFn = std::function<ExpressionFilterPtr(CallParamsInfo params)>;
+
+ ExpressionFilter(const std::string& filterName, CallParamsInfo params);
+
+ InternalValue Evaluate(const InternalValue& baseVal, RenderContext& context);
+ void SetParentFilter(std::shared_ptr<ExpressionFilter> parentFilter)
+ {
+ m_parentFilter = std::move(parentFilter);
+ }
+ bool IsEqual(const IComparable& other) const override
+ {
+ auto* valuePtr = dynamic_cast<const ExpressionFilter*>(&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:
+ ExpressionFilterPtr m_filter;
+ std::shared_ptr<ExpressionFilter> m_parentFilter;
+};
+
+
+class IfExpression : public IComparable
+{
+public:
+ virtual ~IfExpression() {}
+
+ IfExpression(ExpressionEvaluatorPtr<> testExpr, ExpressionEvaluatorPtr<> altValue)
+ : m_testExpr(testExpr)
+ , m_altValue(altValue)
+ {
+ }
+
+ bool Evaluate(RenderContext& context);
+ InternalValue EvaluateAltValue(RenderContext& context);
+
+ void SetAltValue(ExpressionEvaluatorPtr<> altValue)
+ {
+ m_altValue = std::move(altValue);
+ }
+
+ bool IsEqual(const IComparable& other) const override
+ {
+ auto* valPtr = dynamic_cast<const IfExpression*>(&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;
+};
+
+namespace helpers
+{
+ParsedArguments ParseCallParams(const std::initializer_list<ArgumentInfo>& argsInfo, const CallParams& params, bool& isSucceeded);
+ParsedArguments ParseCallParams(const std::vector<ArgumentInfo>& args, const CallParams& params, bool& isSucceeded);
+ParsedArgumentsInfo ParseCallParamsInfo(const std::initializer_list<ArgumentInfo>& argsInfo, const CallParamsInfo& params, bool& isSucceeded);
+ParsedArgumentsInfo ParseCallParamsInfo(const std::vector<ArgumentInfo>& args, const CallParamsInfo& params, bool& isSucceeded);
+CallParams EvaluateCallParams(const CallParamsInfo& info, RenderContext& context);
+} // namespace helpers
+} // namespace jinja2
+
+#endif // JINJA2CPP_SRC_EXPRESSION_EVALUATOR_H
diff --git a/contrib/libs/jinja2cpp/src/expression_parser.cpp b/contrib/libs/jinja2cpp/src/expression_parser.cpp
new file mode 100644
index 0000000000..edc599cc19
--- /dev/null
+++ b/contrib/libs/jinja2cpp/src/expression_parser.cpp
@@ -0,0 +1,606 @@
+#include "expression_parser.h"
+
+#include <sstream>
+#include <unordered_set>
+#include <jinja2cpp/template_env.h>
+
+namespace jinja2
+{
+
+template<typename T>
+auto ReplaceErrorIfPossible(T& result, const Token& pivotTok, ErrorCode newError)
+{
+ auto& error = result.error();
+ if (error.errorToken.range.startOffset == pivotTok.range.startOffset)
+ return MakeParseError(newError, pivotTok);
+
+ return result.get_unexpected();
+}
+
+ExpressionParser::ExpressionParser(const Settings& /* settings */, TemplateEnv* /* env */)
+{
+
+}
+
+ExpressionParser::ParseResult<RendererPtr> ExpressionParser::Parse(LexScanner& lexer)
+{
+ auto evaluator = ParseFullExpression(lexer);
+ if (!evaluator)
+ return evaluator.get_unexpected();
+
+ auto tok = lexer.NextToken();
+ if (tok != Token::Eof)
+ {
+ auto tok1 = tok;
+ tok1.type = Token::Eof;
+
+ return MakeParseError(ErrorCode::ExpectedToken, tok, {tok1});
+ }
+
+ RendererPtr result = std::make_shared<ExpressionRenderer>(*evaluator);
+
+ return result;
+}
+
+ExpressionParser::ParseResult<ExpressionEvaluatorPtr<FullExpressionEvaluator>> ExpressionParser::ParseFullExpression(LexScanner &lexer, bool includeIfPart)
+{
+ ExpressionEvaluatorPtr<FullExpressionEvaluator> result;
+ LexScanner::StateSaver saver(lexer);
+
+ ExpressionEvaluatorPtr<FullExpressionEvaluator> evaluator = std::make_shared<FullExpressionEvaluator>();
+ auto value = ParseLogicalOr(lexer);
+ if (!value)
+ return value.get_unexpected();
+
+ evaluator->SetExpression(*value);
+
+ if (includeIfPart && lexer.EatIfEqual(Keyword::If))
+ {
+ auto ifExpr = ParseIfExpression(lexer);
+ if (!ifExpr)
+ return ifExpr.get_unexpected();
+ evaluator->SetTester(*ifExpr);
+ }
+
+ saver.Commit();
+
+ return evaluator;
+}
+
+ExpressionParser::ParseResult<ExpressionEvaluatorPtr<Expression>> ExpressionParser::ParseLogicalOr(LexScanner& lexer)
+{
+ auto left = ParseLogicalAnd(lexer);
+
+ if (left && lexer.EatIfEqual(Keyword::LogicalOr))
+ {
+ auto right = ParseLogicalOr(lexer);
+ if (!right)
+ return right.get_unexpected();
+
+ return std::make_shared<BinaryExpression>(BinaryExpression::LogicalOr, *left, *right);
+ }
+
+ return left;
+}
+
+ExpressionParser::ParseResult<ExpressionEvaluatorPtr<Expression>> ExpressionParser::ParseLogicalAnd(LexScanner& lexer)
+{
+ auto left = ParseLogicalCompare(lexer);
+
+ if (left && lexer.EatIfEqual(Keyword::LogicalAnd))
+ {
+ auto right = ParseLogicalAnd(lexer);
+ if (!right)
+ return right;
+
+ return std::make_shared<BinaryExpression>(BinaryExpression::LogicalAnd, *left, *right);
+ }
+
+ return left;
+}
+
+ExpressionParser::ParseResult<ExpressionEvaluatorPtr<Expression>> ExpressionParser::ParseLogicalCompare(LexScanner& lexer)
+{
+ auto left = ParseStringConcat(lexer);
+ if (!left)
+ return left;
+
+ auto tok = lexer.NextToken();
+ BinaryExpression::Operation operation;
+ switch (tok.type)
+ {
+ case Token::Equal:
+ operation = BinaryExpression::LogicalEq;
+ break;
+ case Token::NotEqual:
+ operation = BinaryExpression::LogicalNe;
+ break;
+ case '<':
+ operation = BinaryExpression::LogicalLt;
+ break;
+ case '>':
+ operation = BinaryExpression::LogicalGt;
+ break;
+ case Token::GreaterEqual:
+ operation = BinaryExpression::LogicalGe;
+ break;
+ case Token::LessEqual:
+ operation = BinaryExpression::LogicalLe;
+ break;
+ default:
+ switch (lexer.GetAsKeyword(tok))
+ {
+ case Keyword::In:
+ operation = BinaryExpression::In;
+ break;
+ case Keyword::Is:
+ {
+ Token nextTok = lexer.NextToken();
+ if (nextTok != Token::Identifier)
+ return MakeParseError(ErrorCode::ExpectedIdentifier, nextTok);
+
+ std::string name = AsString(nextTok.value);
+ ParseResult<CallParamsInfo> params;
+
+ if (lexer.EatIfEqual('('))
+ params = ParseCallParams(lexer);
+
+ if (!params)
+ return params.get_unexpected();
+
+ return std::make_shared<IsExpression>(*left, std::move(name), std::move(*params));
+ }
+ default:
+ lexer.ReturnToken();
+ return left;
+ }
+ }
+
+ auto right = ParseStringConcat(lexer);
+ if (!right)
+ return right;
+
+ return std::make_shared<BinaryExpression>(operation, *left, *right);
+}
+
+ExpressionParser::ParseResult<ExpressionEvaluatorPtr<Expression>> ExpressionParser::ParseStringConcat(LexScanner& lexer)
+{
+ auto left = ParseMathPow(lexer);
+
+ if (left && lexer.EatIfEqual('~'))
+ {
+ auto right = ParseLogicalAnd(lexer);
+ if (!right)
+ return right;
+
+ return std::make_shared<BinaryExpression>(BinaryExpression::StringConcat, *left, *right);
+ }
+ return left;
+}
+
+ExpressionParser::ParseResult<ExpressionEvaluatorPtr<Expression>> ExpressionParser::ParseMathPow(LexScanner& lexer)
+{
+ auto left = ParseMathPlusMinus(lexer);
+
+ if (left && lexer.EatIfEqual(Token::MulMul))
+ {
+ auto right = ParseMathPow(lexer);
+ if (!right)
+ return right;
+
+ return std::make_shared<BinaryExpression>(BinaryExpression::Pow, *left, *right);
+ }
+
+ return left;
+}
+
+ExpressionParser::ParseResult<ExpressionEvaluatorPtr<Expression>> ExpressionParser::ParseMathPlusMinus(LexScanner& lexer)
+{
+ auto left = ParseMathMulDiv(lexer);
+ if (!left)
+ return left;
+
+ auto tok = lexer.NextToken();
+ BinaryExpression::Operation operation;
+ switch (tok.type)
+ {
+ case '+':
+ operation = BinaryExpression::Plus;
+ break;
+ case '-':
+ operation = BinaryExpression::Minus;
+ break;
+ default:
+ lexer.ReturnToken();
+ return left;
+ }
+
+ auto right = ParseMathPlusMinus(lexer);
+ if (!right)
+ return right;
+
+ return std::make_shared<BinaryExpression>(operation, *left, *right);
+}
+
+ExpressionParser::ParseResult<ExpressionEvaluatorPtr<Expression>> ExpressionParser::ParseMathMulDiv(LexScanner& lexer)
+{
+ auto left = ParseUnaryPlusMinus(lexer);
+ if (!left)
+ return left;
+
+ auto tok = lexer.NextToken();
+ BinaryExpression::Operation operation;
+ switch (tok.type)
+ {
+ case '*':
+ operation = BinaryExpression::Mul;
+ break;
+ case '/':
+ operation = BinaryExpression::Div;
+ break;
+ case Token::DivDiv:
+ operation = BinaryExpression::DivInteger;
+ break;
+ case '%':
+ operation = BinaryExpression::DivReminder;
+ break;
+ default:
+ lexer.ReturnToken();
+ return left;
+ }
+
+ auto right = ParseMathMulDiv(lexer);
+ if (!right)
+ return right;
+
+ return std::make_shared<BinaryExpression>(operation, *left, *right);
+}
+
+ExpressionParser::ParseResult<ExpressionEvaluatorPtr<Expression>> ExpressionParser::ParseUnaryPlusMinus(LexScanner& lexer)
+{
+ const auto tok = lexer.NextToken();
+ const auto isUnary = tok == '+' || tok == '-' || lexer.GetAsKeyword(tok) == Keyword::LogicalNot;
+ if (!isUnary)
+ lexer.ReturnToken();
+
+ auto subExpr = ParseValueExpression(lexer);
+ if (!subExpr)
+ return subExpr;
+
+ ExpressionEvaluatorPtr<Expression> result;
+ if (isUnary)
+ result = std::make_shared<UnaryExpression>(tok == '+' ? UnaryExpression::UnaryPlus : (tok == '-' ? UnaryExpression::UnaryMinus : UnaryExpression::LogicalNot), *subExpr);
+ else
+ result = subExpr.value();
+
+ if (lexer.EatIfEqual('|'))
+ {
+ auto filter = ParseFilterExpression(lexer);
+ if (!filter)
+ return filter.get_unexpected();
+ result = std::make_shared<FilteredExpression>(std::move(result), *filter);
+ }
+
+ return result;
+}
+
+ExpressionParser::ParseResult<ExpressionEvaluatorPtr<Expression>> ExpressionParser::ParseValueExpression(LexScanner& lexer)
+{
+ Token tok = lexer.NextToken();
+ static const std::unordered_set<Keyword> forbiddenKw = {Keyword::Is, Keyword::In, Keyword::If, Keyword::Else};
+
+ ParseResult<ExpressionEvaluatorPtr<Expression>> valueRef;
+
+ switch (tok.type)
+ {
+ case Token::Identifier:
+ {
+ auto kwType = lexer.GetAsKeyword(tok);
+ if (forbiddenKw.count(kwType) != 0)
+ return MakeParseError(ErrorCode::UnexpectedToken, tok);
+
+ valueRef = std::make_shared<ValueRefExpression>(AsString(tok.value));
+ break;
+ }
+ case Token::IntegerNum:
+ case Token::FloatNum:
+ case Token::String:
+ return std::make_shared<ConstantExpression>(tok.value);
+ case Token::True:
+ return std::make_shared<ConstantExpression>(InternalValue(true));
+ case Token::False:
+ return std::make_shared<ConstantExpression>(InternalValue(false));
+ case '(':
+ valueRef = ParseBracedExpressionOrTuple(lexer);
+ break;
+ case '[':
+ valueRef = ParseTuple(lexer);
+ break;
+ case '{':
+ valueRef = ParseDictionary(lexer);
+ break;
+ default:
+ return MakeParseError(ErrorCode::UnexpectedToken, tok);
+ }
+
+ if (valueRef)
+ {
+ tok = lexer.PeekNextToken();
+ if (tok == '[' || tok == '.')
+ valueRef = ParseSubscript(lexer, *valueRef);
+
+ if (lexer.EatIfEqual('('))
+ valueRef = ParseCall(lexer, *valueRef);
+ }
+ return valueRef;
+}
+
+ExpressionParser::ParseResult<ExpressionEvaluatorPtr<Expression>> ExpressionParser::ParseBracedExpressionOrTuple(LexScanner& lexer)
+{
+ ExpressionEvaluatorPtr<Expression> result;
+
+ bool isTuple = false;
+ std::vector<ExpressionEvaluatorPtr<Expression>> exprs;
+ for (;;)
+ {
+ Token pivotTok = lexer.PeekNextToken();
+ auto expr = ParseFullExpression(lexer);
+
+ if (!expr)
+ return ReplaceErrorIfPossible(expr, pivotTok, ErrorCode::ExpectedRoundBracket);
+
+ exprs.push_back(*expr);
+ Token tok = lexer.NextToken();
+ if (tok == ')')
+ break;
+ else if (tok == ',')
+ isTuple = true;
+ }
+
+ if (isTuple)
+ result = std::make_shared<TupleCreator>(std::move(exprs));
+ else
+ result = exprs[0];
+
+ return result;
+}
+
+ExpressionParser::ParseResult<ExpressionEvaluatorPtr<Expression>> ExpressionParser::ParseDictionary(LexScanner& lexer)
+{
+ ExpressionEvaluatorPtr<Expression> result;
+
+ std::unordered_map<std::string, ExpressionEvaluatorPtr<Expression>> items;
+ if (lexer.EatIfEqual(']'))
+ return std::make_shared<DictCreator>(std::move(items));
+
+ do
+ {
+ Token key = lexer.NextToken();
+ if (key != Token::String)
+ return MakeParseError(ErrorCode::ExpectedStringLiteral, key);
+
+ if (!lexer.EatIfEqual('='))
+ {
+ auto tok = lexer.PeekNextToken();
+ auto tok1 = tok;
+ tok1.type = Token::Assign;
+ return MakeParseError(ErrorCode::ExpectedToken, tok, {tok1});
+ }
+
+ auto pivotTok = lexer.PeekNextToken();
+ auto expr = ParseFullExpression(lexer);
+ if (!expr)
+ return ReplaceErrorIfPossible(expr, pivotTok, ErrorCode::ExpectedExpression);
+
+ items[AsString(key.value)] = *expr;
+
+ } while (lexer.EatIfEqual(','));
+
+ auto tok = lexer.NextToken();
+ if (tok != '}')
+ return MakeParseError(ErrorCode::ExpectedCurlyBracket, tok);
+
+ result = std::make_shared<DictCreator>(std::move(items));
+
+ return result;
+}
+
+ExpressionParser::ParseResult<ExpressionEvaluatorPtr<Expression>> ExpressionParser::ParseTuple(LexScanner& lexer)
+{
+ ExpressionEvaluatorPtr<Expression> result;
+
+ std::vector<ExpressionEvaluatorPtr<Expression>> exprs;
+ if (lexer.EatIfEqual(']'))
+ return std::make_shared<TupleCreator>(exprs);
+
+ do
+ {
+ auto expr = ParseFullExpression(lexer);
+ if (!expr)
+ return expr.get_unexpected();
+
+ exprs.push_back(*expr);
+ } while (lexer.EatIfEqual(','));
+
+ auto tok = lexer.NextToken();
+ if (tok != ']')
+ return MakeParseError(ErrorCode::ExpectedSquareBracket, tok);
+
+ result = std::make_shared<TupleCreator>(std::move(exprs));
+
+ return result;
+}
+
+ExpressionParser::ParseResult<ExpressionEvaluatorPtr<Expression>> ExpressionParser::ParseCall(LexScanner& lexer, ExpressionEvaluatorPtr<Expression> valueRef)
+{
+ ExpressionEvaluatorPtr<Expression> result;
+
+ ParseResult<CallParamsInfo> params = ParseCallParams(lexer);
+ if (!params)
+ return params.get_unexpected();
+
+ result = std::make_shared<CallExpression>(valueRef, std::move(*params));
+
+ return result;
+}
+
+ExpressionParser::ParseResult<CallParamsInfo> ExpressionParser::ParseCallParams(LexScanner& lexer)
+{
+ CallParamsInfo result;
+
+ if (lexer.EatIfEqual(')'))
+ return result;
+
+ do
+ {
+ Token tok = lexer.NextToken();
+ std::string paramName;
+ if (tok == Token::Identifier && lexer.PeekNextToken() == '=')
+ {
+ paramName = AsString(tok.value);
+ lexer.EatToken();
+ }
+ else
+ {
+ lexer.ReturnToken();
+ }
+
+ auto valueExpr = ParseFullExpression(lexer);
+ if (!valueExpr)
+ {
+ return valueExpr.get_unexpected();
+ }
+ if (paramName.empty())
+ result.posParams.push_back(*valueExpr);
+ else
+ result.kwParams[paramName] = *valueExpr;
+
+ } while (lexer.EatIfEqual(','));
+
+ auto tok = lexer.NextToken();
+ if (tok != ')')
+ return MakeParseError(ErrorCode::ExpectedRoundBracket, tok);
+
+ return result;
+}
+
+ExpressionParser::ParseResult<ExpressionEvaluatorPtr<Expression>> ExpressionParser::ParseSubscript(LexScanner& lexer, ExpressionEvaluatorPtr<Expression> valueRef)
+{
+ ExpressionEvaluatorPtr<SubscriptExpression> result = std::make_shared<SubscriptExpression>(valueRef);
+ for (Token tok = lexer.NextToken(); tok.type == '.' || tok.type == '['; tok = lexer.NextToken())
+ {
+ ParseResult<ExpressionEvaluatorPtr<Expression>> indexExpr;
+ if (tok == '.')
+ {
+ tok = lexer.NextToken();
+ if (tok.type != Token::Identifier)
+ return MakeParseError(ErrorCode::ExpectedIdentifier, tok);
+
+ auto valueName = AsString(tok.value);
+ indexExpr = std::make_shared<ConstantExpression>(InternalValue(valueName));
+ }
+ else
+ {
+ auto expr = ParseFullExpression(lexer);
+
+ if (!expr)
+ return expr.get_unexpected();
+ else
+ indexExpr = *expr;
+
+ if (!lexer.EatIfEqual(']', &tok))
+ return MakeParseError(ErrorCode::ExpectedSquareBracket, lexer.PeekNextToken());
+ }
+
+ result->AddIndex(*indexExpr);
+ }
+
+ lexer.ReturnToken();
+
+ return result;
+}
+
+ExpressionParser::ParseResult<ExpressionEvaluatorPtr<ExpressionFilter>> ExpressionParser::ParseFilterExpression(LexScanner& lexer)
+{
+ ExpressionEvaluatorPtr<ExpressionFilter> result;
+
+ auto startTok = lexer.PeekNextToken();
+ try
+ {
+ do
+ {
+ Token tok = lexer.NextToken();
+ if (tok != Token::Identifier)
+ return MakeParseError(ErrorCode::ExpectedIdentifier, tok);
+
+ std::string name = AsString(tok.value);
+ ParseResult<CallParamsInfo> params;
+
+ if (lexer.NextToken() == '(')
+ params = ParseCallParams(lexer);
+ else
+ lexer.ReturnToken();
+
+ if (!params)
+ return params.get_unexpected();
+
+ auto filter = std::make_shared<ExpressionFilter>(name, std::move(*params));
+ if (result)
+ {
+ filter->SetParentFilter(result);
+ result = filter;
+ }
+ else
+ result = filter;
+
+ } while (lexer.NextToken() == '|');
+
+ lexer.ReturnToken();
+ }
+ catch (const ParseError& error)
+ {
+ return nonstd::make_unexpected(error);
+ }
+ catch (const std::runtime_error&)
+ {
+ return MakeParseError(ErrorCode::UnexpectedException, startTok);
+ }
+ return result;
+}
+
+ExpressionParser::ParseResult<ExpressionEvaluatorPtr<IfExpression>> ExpressionParser::ParseIfExpression(LexScanner& lexer)
+{
+ ExpressionEvaluatorPtr<IfExpression> result;
+
+ auto startTok = lexer.PeekNextToken();
+ try
+ {
+ auto testExpr = ParseLogicalOr(lexer);
+ if (!testExpr)
+ return testExpr.get_unexpected();
+
+ ParseResult<ExpressionEvaluatorPtr<>> altValue;
+ if (lexer.GetAsKeyword(lexer.PeekNextToken()) == Keyword::Else)
+ {
+ lexer.EatToken();
+ auto value = ParseFullExpression(lexer);
+ if (!value)
+ return value.get_unexpected();
+ altValue = *value;
+ }
+
+ result = std::make_shared<IfExpression>(*testExpr, *altValue);
+ }
+ catch (const ParseError& error)
+ {
+ return nonstd::make_unexpected(error);
+ }
+ catch (const std::runtime_error& ex)
+ {
+ std::cout << "Filter parsing problem: " << ex.what() << std::endl;
+ }
+
+ return result;
+}
+
+} // namespace jinja2
diff --git a/contrib/libs/jinja2cpp/src/expression_parser.h b/contrib/libs/jinja2cpp/src/expression_parser.h
new file mode 100644
index 0000000000..dd0b78458f
--- /dev/null
+++ b/contrib/libs/jinja2cpp/src/expression_parser.h
@@ -0,0 +1,49 @@
+#ifndef JINJA2CPP_SRC_EXPRESSION_PARSER_H
+#define JINJA2CPP_SRC_EXPRESSION_PARSER_H
+
+#include "lexer.h"
+#include "error_handling.h"
+#include "expression_evaluator.h"
+#include "renderer.h"
+
+#include <contrib/restricted/expected-lite/include/nonstd/expected.hpp>
+#include <jinja2cpp/template_env.h>
+
+namespace jinja2
+{
+class ExpressionParser
+{
+public:
+ template<typename T>
+ using ParseResult = nonstd::expected<T, ParseError>;
+
+ explicit ExpressionParser(const Settings& settings, TemplateEnv* env = nullptr);
+ ParseResult<RendererPtr> Parse(LexScanner& lexer);
+ ParseResult<ExpressionEvaluatorPtr<FullExpressionEvaluator>> ParseFullExpression(LexScanner& lexer, bool includeIfPart = true);
+ ParseResult<CallParamsInfo> ParseCallParams(LexScanner& lexer);
+ ParseResult<ExpressionEvaluatorPtr<ExpressionFilter>> ParseFilterExpression(LexScanner& lexer);
+private:
+ ParseResult<ExpressionEvaluatorPtr<Expression>> ParseLogicalNot(LexScanner& lexer);
+ ParseResult<ExpressionEvaluatorPtr<Expression>> ParseLogicalOr(LexScanner& lexer);
+ ParseResult<ExpressionEvaluatorPtr<Expression>> ParseLogicalAnd(LexScanner& lexer);
+ ParseResult<ExpressionEvaluatorPtr<Expression>> ParseLogicalCompare(LexScanner& lexer);
+ ParseResult<ExpressionEvaluatorPtr<Expression>> ParseStringConcat(LexScanner& lexer);
+ ParseResult<ExpressionEvaluatorPtr<Expression>> ParseMathPow(LexScanner& lexer);
+ ParseResult<ExpressionEvaluatorPtr<Expression>> ParseMathMulDiv(LexScanner& lexer);
+ ParseResult<ExpressionEvaluatorPtr<Expression>> ParseMathPlusMinus(LexScanner& lexer);
+ ParseResult<ExpressionEvaluatorPtr<Expression>> ParseUnaryPlusMinus(LexScanner& lexer);
+ ParseResult<ExpressionEvaluatorPtr<Expression>> ParseValueExpression(LexScanner& lexer);
+ ParseResult<ExpressionEvaluatorPtr<Expression>> ParseBracedExpressionOrTuple(LexScanner& lexer);
+ ParseResult<ExpressionEvaluatorPtr<Expression>> ParseDictionary(LexScanner& lexer);
+ ParseResult<ExpressionEvaluatorPtr<Expression>> ParseTuple(LexScanner& lexer);
+ ParseResult<ExpressionEvaluatorPtr<Expression>> ParseCall(LexScanner& lexer, ExpressionEvaluatorPtr<Expression> valueRef);
+ ParseResult<ExpressionEvaluatorPtr<Expression>> ParseSubscript(LexScanner& lexer, ExpressionEvaluatorPtr<Expression> valueRef);
+ ParseResult<ExpressionEvaluatorPtr<IfExpression>> ParseIfExpression(LexScanner& lexer);
+
+private:
+ ComposedRenderer* m_topLevelRenderer = nullptr;
+};
+
+} // namespace jinja2
+
+#endif // JINJA2CPP_SRC_EXPRESSION_PARSER_H
diff --git a/contrib/libs/jinja2cpp/src/filesystem_handler.cpp b/contrib/libs/jinja2cpp/src/filesystem_handler.cpp
new file mode 100644
index 0000000000..8325487aff
--- /dev/null
+++ b/contrib/libs/jinja2cpp/src/filesystem_handler.cpp
@@ -0,0 +1,163 @@
+#include <jinja2cpp/filesystem_handler.h>
+#include <jinja2cpp/string_helpers.h>
+
+#include <boost/filesystem/path.hpp>
+#include <boost/filesystem/operations.hpp>
+
+#include <sstream>
+#include <fstream>
+
+namespace jinja2
+{
+
+using TargetFileStream = std::variant<CharFileStreamPtr*, WCharFileStreamPtr*>;
+
+struct FileContentConverter
+{
+ void operator() (const std::string& content, CharFileStreamPtr* sPtr) const
+ {
+ sPtr->reset(new std::istringstream(content));
+ }
+
+ void operator() (const std::wstring& content, WCharFileStreamPtr* sPtr) const
+ {
+ sPtr->reset(new std::wistringstream(content));
+ }
+ void operator() (const std::wstring&, CharFileStreamPtr*) const
+ {
+// CharFileStreamPtr stream(new std::istringstream(content), [](std::istream* s) {delete static_cast<std::istringstream>(s);});
+// std::swap(*sPtr, stream);
+ }
+
+ void operator() (const std::string&, WCharFileStreamPtr*) const
+ {
+// WCharFileStreamPtr stream(new std::wistringstream(content), [](std::wistream* s) {delete static_cast<std::wistringstream>(s);});
+// std::swap(*sPtr, stream);
+ }
+};
+
+void MemoryFileSystem::AddFile(std::string fileName, std::string fileContent)
+{
+ m_filesMap[std::move(fileName)] = FileContent{std::move(fileContent), {}};
+}
+
+void MemoryFileSystem::AddFile(std::string fileName, std::wstring fileContent)
+{
+ m_filesMap[std::move(fileName)] = FileContent{ {}, std::move(fileContent) };
+}
+
+CharFileStreamPtr MemoryFileSystem::OpenStream(const std::string& name) const
+{
+ CharFileStreamPtr result(nullptr, [](std::istream* s) {delete static_cast<std::istringstream*>(s);});
+ auto p = m_filesMap.find(name);
+ if (p == m_filesMap.end())
+ return result;
+
+ auto& content = p->second;
+
+ if (!content.narrowContent && !content.wideContent)
+ return result;
+
+ if (!content.narrowContent)
+ content.narrowContent = ConvertString<std::string>(content.wideContent.value());
+
+ result.reset(new std::istringstream(content.narrowContent.value()));
+
+ return result;
+}
+
+WCharFileStreamPtr MemoryFileSystem::OpenWStream(const std::string& name) const
+{
+ WCharFileStreamPtr result(nullptr, [](std::wistream* s) {delete static_cast<std::wistringstream*>(s);});
+ auto p = m_filesMap.find(name);
+ if (p == m_filesMap.end())
+ return result;
+
+ auto& content = p->second;
+
+ if (!content.narrowContent && !content.wideContent)
+ return result;
+
+ if (!content.wideContent)
+ content.wideContent = ConvertString<std::wstring>(content.narrowContent.value());
+
+ result.reset(new std::wistringstream(content.wideContent.value()));
+
+ return result;
+}
+std::optional<std::chrono::system_clock::time_point> MemoryFileSystem::GetLastModificationDate(const std::string&) const
+{
+ return std::optional<std::chrono::system_clock::time_point>();
+}
+
+bool MemoryFileSystem::IsEqual(const IComparable& other) const
+{
+ auto* ptr = dynamic_cast<const MemoryFileSystem*>(&other);
+ if (!ptr)
+ return false;
+ return m_filesMap == ptr->m_filesMap;
+}
+
+RealFileSystem::RealFileSystem(std::string rootFolder)
+ : m_rootFolder(std::move(rootFolder))
+{
+
+}
+
+std::string RealFileSystem::GetFullFilePath(const std::string& name) const
+{
+ boost::filesystem::path root(m_rootFolder);
+ root /= name;
+ return root.string();
+}
+
+CharFileStreamPtr RealFileSystem::OpenStream(const std::string& name) const
+{
+ auto filePath = GetFullFilePath(name);
+
+ CharFileStreamPtr result(new std::ifstream(filePath), [](std::istream* s) {delete static_cast<std::ifstream*>(s);});
+ if (result->good())
+ return result;
+
+ return CharFileStreamPtr(nullptr, [](std::istream*){});
+}
+
+WCharFileStreamPtr RealFileSystem::OpenWStream(const std::string& name) const
+{
+ auto filePath = GetFullFilePath(name);
+
+ WCharFileStreamPtr result(new std::wifstream(filePath), [](std::wistream* s) {delete static_cast<std::wifstream*>(s);});
+ if (result->good())
+ return result;
+
+ return WCharFileStreamPtr(nullptr, [](std::wistream*){;});
+}
+std::optional<std::chrono::system_clock::time_point> RealFileSystem::GetLastModificationDate(const std::string& name) const
+{
+ boost::filesystem::path root(m_rootFolder);
+ root /= name;
+
+ auto modify_time = boost::filesystem::last_write_time(root);
+
+ return std::chrono::system_clock::from_time_t(modify_time);
+}
+CharFileStreamPtr RealFileSystem::OpenByteStream(const std::string& name) const
+{
+ auto filePath = GetFullFilePath(name);
+
+ CharFileStreamPtr result(new std::ifstream(filePath, std::ios_base::binary), [](std::istream* s) {delete static_cast<std::ifstream*>(s);});
+ if (result->good())
+ return result;
+
+ return CharFileStreamPtr(nullptr, [](std::istream*){});
+}
+
+bool RealFileSystem::IsEqual(const IComparable& other) const
+{
+ auto* ptr = dynamic_cast<const RealFileSystem*>(&other);
+ if (!ptr)
+ return false;
+ return m_rootFolder == ptr->m_rootFolder;
+}
+
+} // namespace jinja2
diff --git a/contrib/libs/jinja2cpp/src/filters.cpp b/contrib/libs/jinja2cpp/src/filters.cpp
new file mode 100644
index 0000000000..5e0725be96
--- /dev/null
+++ b/contrib/libs/jinja2cpp/src/filters.cpp
@@ -0,0 +1,1086 @@
+#include "filters.h"
+
+#include "binding/rapid_json_serializer.h"
+#include "generic_adapters.h"
+#include "out_stream.h"
+#include "testers.h"
+#include "value_helpers.h"
+#include "value_visitors.h"
+
+#include <algorithm>
+#include <numeric>
+#include <random>
+#include <sstream>
+#include <string>
+
+using namespace std::string_literals;
+
+namespace jinja2
+{
+
+template<typename F>
+struct FilterFactory
+{
+ static FilterPtr Create(FilterParams params) { return std::make_shared<F>(std::move(params)); }
+
+ template<typename... Args>
+ static ExpressionFilter::FilterFactoryFn MakeCreator(Args&&... args)
+ {
+ return [args...](FilterParams params) { return std::make_shared<F>(std::move(params), args...); };
+ }
+};
+
+std::unordered_map<std::string, ExpressionFilter::FilterFactoryFn> s_filters = {
+ { "abs", FilterFactory<filters::ValueConverter>::MakeCreator(filters::ValueConverter::AbsMode) },
+ { "applymacro", &FilterFactory<filters::ApplyMacro>::Create },
+ { "attr", &FilterFactory<filters::Attribute>::Create },
+ { "batch", FilterFactory<filters::Slice>::MakeCreator(filters::Slice::BatchMode) },
+ { "camelize", FilterFactory<filters::StringConverter>::MakeCreator(filters::StringConverter::CamelMode) },
+ { "capitalize", FilterFactory<filters::StringConverter>::MakeCreator(filters::StringConverter::CapitalMode) },
+ { "center", FilterFactory<filters::StringConverter>::MakeCreator(filters::StringConverter::CenterMode) },
+ { "default", &FilterFactory<filters::Default>::Create },
+ { "d", &FilterFactory<filters::Default>::Create },
+ { "dictsort", &FilterFactory<filters::DictSort>::Create },
+ { "escape", FilterFactory<filters::StringConverter>::MakeCreator(filters::StringConverter::EscapeHtmlMode) },
+ { "escapecpp", FilterFactory<filters::StringConverter>::MakeCreator(filters::StringConverter::EscapeCppMode) },
+ { "first", FilterFactory<filters::SequenceAccessor>::MakeCreator(filters::SequenceAccessor::FirstItemMode) },
+ { "float", FilterFactory<filters::ValueConverter>::MakeCreator(filters::ValueConverter::ToFloatMode) },
+ { "format", FilterFactory<filters::StringFormat>::Create },
+ { "groupby", &FilterFactory<filters::GroupBy>::Create },
+ { "int", FilterFactory<filters::ValueConverter>::MakeCreator(filters::ValueConverter::ToIntMode) },
+ { "join", &FilterFactory<filters::Join>::Create },
+ { "last", FilterFactory<filters::SequenceAccessor>::MakeCreator(filters::SequenceAccessor::LastItemMode) },
+ { "length", FilterFactory<filters::SequenceAccessor>::MakeCreator(filters::SequenceAccessor::LengthMode) },
+ { "list", FilterFactory<filters::ValueConverter>::MakeCreator(filters::ValueConverter::ToListMode) },
+ { "lower", FilterFactory<filters::StringConverter>::MakeCreator(filters::StringConverter::LowerMode) },
+ { "map", &FilterFactory<filters::Map>::Create },
+ { "max", FilterFactory<filters::SequenceAccessor>::MakeCreator(filters::SequenceAccessor::MaxItemMode) },
+ { "min", FilterFactory<filters::SequenceAccessor>::MakeCreator(filters::SequenceAccessor::MinItemMode) },
+ { "pprint", &FilterFactory<filters::PrettyPrint>::Create },
+ { "random", FilterFactory<filters::SequenceAccessor>::MakeCreator(filters::SequenceAccessor::RandomMode) },
+ { "reject", FilterFactory<filters::Tester>::MakeCreator(filters::Tester::RejectMode) },
+ { "rejectattr", FilterFactory<filters::Tester>::MakeCreator(filters::Tester::RejectAttrMode) },
+ { "replace", FilterFactory<filters::StringConverter>::MakeCreator(filters::StringConverter::ReplaceMode) },
+ { "round", FilterFactory<filters::ValueConverter>::MakeCreator(filters::ValueConverter::RoundMode) },
+ { "reverse", FilterFactory<filters::SequenceAccessor>::MakeCreator(filters::SequenceAccessor::ReverseMode) },
+ { "select", FilterFactory<filters::Tester>::MakeCreator(filters::Tester::SelectMode) },
+ { "selectattr", FilterFactory<filters::Tester>::MakeCreator(filters::Tester::SelectAttrMode) },
+ { "slice", FilterFactory<filters::Slice>::MakeCreator(filters::Slice::SliceMode) },
+ { "sort", &FilterFactory<filters::Sort>::Create },
+ { "striptags", FilterFactory<filters::StringConverter>::MakeCreator(filters::StringConverter::StriptagsMode) },
+ { "sum", FilterFactory<filters::SequenceAccessor>::MakeCreator(filters::SequenceAccessor::SumItemsMode) },
+ { "title", FilterFactory<filters::StringConverter>::MakeCreator(filters::StringConverter::TitleMode) },
+ { "tojson", FilterFactory<filters::Serialize>::MakeCreator(filters::Serialize::JsonMode) },
+ { "toxml", FilterFactory<filters::Serialize>::MakeCreator(filters::Serialize::XmlMode) },
+ { "toyaml", FilterFactory<filters::Serialize>::MakeCreator(filters::Serialize::YamlMode) },
+ { "trim", FilterFactory<filters::StringConverter>::MakeCreator(filters::StringConverter::TrimMode) },
+ { "truncate", FilterFactory<filters::StringConverter>::MakeCreator(filters::StringConverter::TruncateMode) },
+ { "unique", FilterFactory<filters::SequenceAccessor>::MakeCreator(filters::SequenceAccessor::UniqueItemsMode) },
+ { "upper", FilterFactory<filters::StringConverter>::MakeCreator(filters::StringConverter::UpperMode) },
+ { "urlencode", FilterFactory<filters::StringConverter>::MakeCreator(filters::StringConverter::UrlEncodeMode) },
+ { "wordcount", FilterFactory<filters::StringConverter>::MakeCreator(filters::StringConverter::WordCountMode) },
+ { "wordwrap", FilterFactory<filters::StringConverter>::MakeCreator(filters::StringConverter::WordWrapMode) },
+ { "underscorize", FilterFactory<filters::StringConverter>::MakeCreator(filters::StringConverter::UnderscoreMode) },
+ { "xmlattr", &FilterFactory<filters::XmlAttrFilter>::Create }
+};
+
+extern FilterPtr CreateFilter(std::string filterName, CallParamsInfo params)
+{
+ auto p = s_filters.find(filterName);
+ if (p == s_filters.end())
+ return std::make_shared<filters::UserDefinedFilter>(std::move(filterName), std::move(params));
+
+ return p->second(std::move(params));
+}
+
+namespace filters
+{
+
+Join::Join(FilterParams params)
+{
+ ParseParams({ { "d", false, std::string() }, { "attribute" } }, params);
+}
+
+InternalValue Join::Filter(const InternalValue& baseVal, RenderContext& context)
+{
+ InternalValue attrName = GetArgumentValue("attribute", context);
+
+ bool isConverted = false;
+ ListAdapter values = ConvertToList(baseVal, attrName, isConverted);
+
+ if (!isConverted)
+ return InternalValue();
+
+ bool isFirst = true;
+ InternalValue result;
+ InternalValue delimiter = m_args["d"]->Evaluate(context);
+ for (const InternalValue& val : values)
+ {
+ if (isFirst)
+ isFirst = false;
+ else
+ result = Apply2<visitors::StringJoiner>(result, delimiter);
+
+ result = Apply2<visitors::StringJoiner>(result, val);
+ }
+
+ return result;
+}
+
+Sort::Sort(FilterParams params)
+{
+ ParseParams({ { "reverse", false, InternalValue(false) }, { "case_sensitive", false, InternalValue(false) }, { "attribute", false } }, params);
+}
+
+InternalValue Sort::Filter(const InternalValue& baseVal, RenderContext& context)
+{
+ InternalValue attrName = GetArgumentValue("attribute", context);
+ InternalValue isReverseVal = GetArgumentValue("reverse", context, InternalValue(false));
+ InternalValue isCsVal = GetArgumentValue("case_sensitive", context, InternalValue(false));
+
+ bool isConverted = false;
+ ListAdapter origValues = ConvertToList(baseVal, isConverted);
+ if (!isConverted)
+ return InternalValue();
+ InternalValueList values = origValues.ToValueList();
+
+ BinaryExpression::Operation oper = ConvertToBool(isReverseVal) ? BinaryExpression::LogicalGt : BinaryExpression::LogicalLt;
+ BinaryExpression::CompareType compType = ConvertToBool(isCsVal) ? BinaryExpression::CaseSensitive : BinaryExpression::CaseInsensitive;
+
+ std::sort(values.begin(), values.end(), [&attrName, oper, compType, &context](auto& val1, auto& val2) {
+ InternalValue cmpRes;
+ if (IsEmpty(attrName))
+ cmpRes = Apply2<visitors::BinaryMathOperation>(val1, val2, oper, compType);
+ else
+ cmpRes = Apply2<visitors::BinaryMathOperation>(Subscript(val1, attrName, &context), Subscript(val2, attrName, &context), oper, compType);
+
+ return ConvertToBool(cmpRes);
+ });
+
+ return ListAdapter::CreateAdapter(std::move(values));
+}
+
+Attribute::Attribute(FilterParams params)
+{
+ ParseParams({ { "name", true }, { "default", false } }, params);
+}
+
+InternalValue Attribute::Filter(const InternalValue& baseVal, RenderContext& context)
+{
+ const auto attrNameVal = GetArgumentValue("name", context);
+ const auto result = Subscript(baseVal, attrNameVal, &context);
+ if (result.IsEmpty())
+ return GetArgumentValue("default", context);
+ return result;
+}
+
+Default::Default(FilterParams params)
+{
+ ParseParams({ { "default_value", false, InternalValue(""s) }, { "boolean", false, InternalValue(false) } }, params);
+}
+
+InternalValue Default::Filter(const InternalValue& baseVal, RenderContext& context)
+{
+ InternalValue defaultVal = GetArgumentValue("default_value", context);
+ InternalValue conditionResult = GetArgumentValue("boolean", context);
+
+ if (IsEmpty(baseVal))
+ return defaultVal;
+
+ if (ConvertToBool(conditionResult) && !ConvertToBool(baseVal))
+ return defaultVal;
+
+ return baseVal;
+}
+
+DictSort::DictSort(FilterParams params)
+{
+ ParseParams({ { "case_sensitive", false }, { "by", false, "key"s }, { "reverse", false } }, params);
+}
+
+InternalValue DictSort::Filter(const InternalValue& baseVal, RenderContext& context)
+{
+ const MapAdapter* map = GetIf<MapAdapter>(&baseVal);
+ if (map == nullptr)
+ return InternalValue();
+
+ InternalValue isReverseVal = GetArgumentValue("reverse", context);
+ InternalValue isCsVal = GetArgumentValue("case_sensitive", context);
+ InternalValue byVal = GetArgumentValue("by", context);
+
+ bool (*comparator)(const KeyValuePair& left, const KeyValuePair& right);
+
+ if (AsString(byVal) == "key") // Sort by key
+ {
+ if (ConvertToBool(isCsVal))
+ {
+ comparator = [](const KeyValuePair& left, const KeyValuePair& right) { return left.key < right.key; };
+ }
+ else
+ {
+ comparator = [](const KeyValuePair& left, const KeyValuePair& right) {
+ return boost::lexicographical_compare(left.key, right.key, boost::algorithm::is_iless());
+ };
+ }
+ }
+ else if (AsString(byVal) == "value")
+ {
+ if (ConvertToBool(isCsVal))
+ {
+ comparator = [](const KeyValuePair& left, const KeyValuePair& right) {
+ return ConvertToBool(
+ Apply2<visitors::BinaryMathOperation>(left.value, right.value, BinaryExpression::LogicalLt, BinaryExpression::CaseSensitive));
+ };
+ }
+ else
+ {
+ comparator = [](const KeyValuePair& left, const KeyValuePair& right) {
+ return ConvertToBool(
+ Apply2<visitors::BinaryMathOperation>(left.value, right.value, BinaryExpression::LogicalLt, BinaryExpression::CaseInsensitive));
+ };
+ }
+ }
+ else
+ return InternalValue();
+
+ std::vector<KeyValuePair> tempVector;
+ tempVector.reserve(map->GetSize());
+ for (auto& key : map->GetKeys())
+ {
+ auto val = map->GetValueByName(key);
+ tempVector.push_back(KeyValuePair{ key, val });
+ }
+
+ if (ConvertToBool(isReverseVal))
+ std::sort(tempVector.begin(), tempVector.end(), [comparator](auto& l, auto& r) { return comparator(r, l); });
+ else
+ std::sort(tempVector.begin(), tempVector.end(), [comparator](auto& l, auto& r) { return comparator(l, r); });
+
+ InternalValueList resultList;
+ for (auto& tmpVal : tempVector)
+ {
+ auto resultVal = InternalValue(std::move(tmpVal));
+ if (baseVal.ShouldExtendLifetime())
+ resultVal.SetParentData(baseVal);
+ resultList.push_back(std::move(resultVal));
+ }
+
+ return InternalValue(ListAdapter::CreateAdapter(std::move(resultList)));
+}
+
+GroupBy::GroupBy(FilterParams params)
+{
+ ParseParams({ { "attribute", true } }, params);
+}
+
+InternalValue GroupBy::Filter(const InternalValue& baseVal, RenderContext& context)
+{
+ bool isConverted = false;
+ ListAdapter list = ConvertToList(baseVal, isConverted);
+
+ if (!isConverted)
+ return InternalValue();
+
+ InternalValue attrName = GetArgumentValue("attribute", context);
+
+ auto equalComparator = [](auto& val1, auto& val2) {
+ InternalValue cmpRes = Apply2<visitors::BinaryMathOperation>(val1, val2, BinaryExpression::LogicalEq, BinaryExpression::CaseSensitive);
+
+ return ConvertToBool(cmpRes);
+ };
+
+ struct GroupInfo
+ {
+ InternalValue grouper;
+ InternalValueList items;
+ };
+
+ std::vector<GroupInfo> groups;
+
+ for (auto& item : list)
+ {
+ auto attr = Subscript(item, attrName, &context);
+ auto p = std::find_if(groups.begin(), groups.end(), [&equalComparator, &attr](auto& i) { return equalComparator(i.grouper, attr); });
+ if (p == groups.end())
+ groups.push_back(GroupInfo{ attr, { item } });
+ else
+ p->items.push_back(item);
+ }
+
+ InternalValueList result;
+ for (auto& g : groups)
+ {
+ InternalValueMap groupItem{ { "grouper", std::move(g.grouper) }, { "list", ListAdapter::CreateAdapter(std::move(g.items)) } };
+ result.push_back(CreateMapAdapter(std::move(groupItem)));
+ }
+
+ return ListAdapter::CreateAdapter(std::move(result));
+}
+
+ApplyMacro::ApplyMacro(FilterParams params)
+{
+ ParseParams({ { "macro", true } }, params);
+ m_mappingParams.kwParams = m_args.extraKwArgs;
+ m_mappingParams.posParams = m_args.extraPosArgs;
+}
+
+InternalValue ApplyMacro::Filter(const InternalValue& baseVal, RenderContext& context)
+{
+ InternalValue macroName = GetArgumentValue("macro", context);
+ if (IsEmpty(macroName))
+ return InternalValue();
+
+ bool macroFound = false;
+ auto macroValPtr = context.FindValue(AsString(macroName), macroFound);
+ if (!macroFound)
+ return InternalValue();
+
+ const Callable* callable = GetIf<Callable>(&macroValPtr->second);
+ if (callable == nullptr || callable->GetKind() != Callable::Macro)
+ return InternalValue();
+
+ CallParams tmpCallParams = helpers::EvaluateCallParams(m_mappingParams, context);
+ CallParams callParams;
+ callParams.kwParams = std::move(tmpCallParams.kwParams);
+ callParams.posParams.reserve(tmpCallParams.posParams.size() + 1);
+ callParams.posParams.push_back(baseVal);
+ for (auto& p : tmpCallParams.posParams)
+ callParams.posParams.push_back(std::move(p));
+
+ InternalValue result;
+ if (callable->GetType() == Callable::Type::Expression)
+ {
+ result = callable->GetExpressionCallable()(callParams, context);
+ }
+ else
+ {
+ TargetString resultStr;
+ auto stream = context.GetRendererCallback()->GetStreamOnString(resultStr);
+ callable->GetStatementCallable()(callParams, stream, context);
+ result = std::move(resultStr);
+ }
+
+ return result;
+}
+
+Map::Map(FilterParams params)
+{
+ ParseParams({ { "filter", true } }, MakeParams(std::move(params)));
+ m_mappingParams.kwParams = m_args.extraKwArgs;
+ m_mappingParams.posParams = m_args.extraPosArgs;
+}
+
+FilterParams Map::MakeParams(FilterParams params)
+{
+ if (!params.posParams.empty() || params.kwParams.empty() || params.kwParams.size() > 2)
+ {
+ return params;
+ }
+
+ const auto attributeIt = params.kwParams.find("attribute");
+ if (attributeIt == params.kwParams.cend())
+ {
+ return params;
+ }
+
+ FilterParams result;
+ result.kwParams["name"] = attributeIt->second;
+ result.kwParams["filter"] = std::make_shared<ConstantExpression>("attr"s);
+
+ const auto defaultIt = params.kwParams.find("default");
+ if (defaultIt != params.kwParams.cend())
+ result.kwParams["default"] = defaultIt->second;
+
+ return result;
+}
+
+InternalValue Map::Filter(const InternalValue& baseVal, RenderContext& context)
+{
+ InternalValue filterName = GetArgumentValue("filter", context);
+ if (IsEmpty(filterName))
+ return InternalValue();
+
+ auto filter = CreateFilter(AsString(filterName), m_mappingParams);
+ if (!filter)
+ return InternalValue();
+
+ bool isConverted = false;
+ auto list = ConvertToList(baseVal, isConverted);
+ if (!isConverted)
+ return InternalValue();
+
+ InternalValueList resultList;
+ resultList.reserve(list.GetSize().value_or(0));
+ std::transform(list.begin(), list.end(), std::back_inserter(resultList), [filter, &context](auto& val) { return filter->Filter(val, context); });
+
+ return ListAdapter::CreateAdapter(std::move(resultList));
+}
+Random::Random(FilterParams params) {}
+
+InternalValue Random::Filter(const InternalValue&, RenderContext&)
+{
+ return InternalValue();
+}
+
+SequenceAccessor::SequenceAccessor(FilterParams params, SequenceAccessor::Mode mode)
+ : m_mode(mode)
+{
+ switch (mode)
+ {
+ case FirstItemMode:
+ break;
+ case LastItemMode:
+ break;
+ case LengthMode:
+ break;
+ case MaxItemMode:
+ case MinItemMode:
+ ParseParams({ { "case_sensitive", false, InternalValue(false) }, { "attribute", false } }, params);
+ break;
+ case RandomMode:
+ case ReverseMode:
+ break;
+ case SumItemsMode:
+ ParseParams({ { "attribute", false }, { "start", false } }, params);
+ break;
+ case UniqueItemsMode:
+ ParseParams({ { "attribute", false } }, params);
+ break;
+ }
+}
+
+InternalValue SequenceAccessor::Filter(const InternalValue& baseVal, RenderContext& context)
+{
+ InternalValue result;
+
+ bool isConverted = false;
+ ListAdapter list = ConvertToList(baseVal, isConverted);
+
+ if (!isConverted)
+ return result;
+
+ InternalValue attrName = GetArgumentValue("attribute", context);
+ InternalValue isCsVal = GetArgumentValue("case_sensitive", context, InternalValue(false));
+
+ BinaryExpression::CompareType compType = ConvertToBool(isCsVal) ? BinaryExpression::CaseSensitive : BinaryExpression::CaseInsensitive;
+
+ auto lessComparator = [&attrName, &compType, &context](auto& val1, auto& val2) {
+ InternalValue cmpRes;
+
+ if (IsEmpty(attrName))
+ cmpRes = Apply2<visitors::BinaryMathOperation>(val1, val2, BinaryExpression::LogicalLt, compType);
+ else
+ cmpRes = Apply2<visitors::BinaryMathOperation>(
+ Subscript(val1, attrName, &context), Subscript(val2, attrName, &context), BinaryExpression::LogicalLt, compType);
+
+ return ConvertToBool(cmpRes);
+ };
+
+ const auto& listSize = list.GetSize();
+
+ switch (m_mode)
+ {
+ case FirstItemMode:
+ if (listSize)
+ result = list.GetValueByIndex(0);
+ else
+ {
+ auto it = list.begin();
+ if (it != list.end())
+ result = *it;
+ }
+ break;
+ case LastItemMode:
+ if (listSize)
+ result = list.GetValueByIndex(listSize.value() - 1);
+ else
+ {
+ auto it = list.begin();
+ auto end = list.end();
+ for (; it != end; ++it)
+ result = *it;
+ }
+ break;
+ case LengthMode:
+ if (listSize)
+ result = static_cast<int64_t>(listSize.value());
+ else
+ result = static_cast<int64_t>(std::distance(list.begin(), list.end()));
+ break;
+ case RandomMode:
+ {
+ std::random_device rd;
+ std::mt19937 gen(rd());
+ if (listSize)
+ {
+ std::uniform_int_distribution<> dis(0, static_cast<int>(listSize.value()) - 1);
+ result = list.GetValueByIndex(dis(gen));
+ }
+ else
+ {
+ auto it = list.begin();
+ auto end = list.end();
+ size_t count = 0;
+ for (; it != end; ++it, ++count)
+ {
+ bool doCopy = count == 0 || std::uniform_int_distribution<size_t>(0, count)(gen) == 0;
+ if (doCopy)
+ result = *it;
+ }
+ }
+ break;
+ }
+ case MaxItemMode:
+ {
+ auto b = list.begin();
+ auto e = list.end();
+ auto p = std::max_element(list.begin(), list.end(), lessComparator);
+ result = p != e ? *p : InternalValue();
+ break;
+ }
+ case MinItemMode:
+ {
+ auto b = list.begin();
+ auto e = list.end();
+ auto p = std::min_element(b, e, lessComparator);
+ result = p != e ? *p : InternalValue();
+ break;
+ }
+ case ReverseMode:
+ {
+ if (listSize)
+ {
+ auto size = listSize.value();
+ InternalValueList resultList(size);
+ for (std::size_t n = 0; n < size; ++n)
+ resultList[size - n - 1] = list.GetValueByIndex(n);
+ result = ListAdapter::CreateAdapter(std::move(resultList));
+ }
+ else
+ {
+ InternalValueList resultList;
+ auto it = list.begin();
+ auto end = list.end();
+ for (; it != end; ++it)
+ resultList.push_back(*it);
+
+ std::reverse(resultList.begin(), resultList.end());
+ result = ListAdapter::CreateAdapter(std::move(resultList));
+ }
+
+ break;
+ }
+ case SumItemsMode:
+ {
+ ListAdapter l1;
+ ListAdapter* actualList;
+ if (IsEmpty(attrName))
+ {
+ actualList = &list;
+ }
+ else
+ {
+ l1 = list.ToSubscriptedList(attrName, true);
+ actualList = &l1;
+ }
+ InternalValue start = GetArgumentValue("start", context);
+ InternalValue resultVal = std::accumulate(actualList->begin(), actualList->end(), start, [](const InternalValue& cur, const InternalValue& val) {
+ if (IsEmpty(cur))
+ return val;
+
+ return Apply2<visitors::BinaryMathOperation>(cur, val, BinaryExpression::Plus);
+ });
+
+ result = std::move(resultVal);
+ break;
+ }
+ case UniqueItemsMode:
+ {
+ InternalValueList resultList;
+
+ struct Item
+ {
+ InternalValue val;
+ int64_t idx;
+ };
+ std::vector<Item> items;
+
+ int idx = 0;
+ for (auto& v : list)
+ items.push_back(Item{ IsEmpty(attrName) ? v : Subscript(v, attrName, &context), idx++ });
+
+ std::stable_sort(items.begin(), items.end(), [&compType](auto& i1, auto& i2) {
+ auto cmpRes = Apply2<visitors::BinaryMathOperation>(i1.val, i2.val, BinaryExpression::LogicalLt, compType);
+
+ return ConvertToBool(cmpRes);
+ });
+
+ auto end = std::unique(items.begin(), items.end(), [&compType](auto& i1, auto& i2) {
+ auto cmpRes = Apply2<visitors::BinaryMathOperation>(i1.val, i2.val, BinaryExpression::LogicalEq, compType);
+
+ return ConvertToBool(cmpRes);
+ });
+ items.erase(end, items.end());
+
+ std::stable_sort(items.begin(), items.end(), [](auto& i1, auto& i2) { return i1.idx < i2.idx; });
+
+ for (auto& i : items)
+ resultList.push_back(list.GetValueByIndex(i.idx));
+
+ result = ListAdapter::CreateAdapter(std::move(resultList));
+ break;
+ }
+ }
+
+ return result;
+}
+Slice::Slice(FilterParams params, Slice::Mode mode)
+ : m_mode{ mode }
+{
+ if (m_mode == BatchMode)
+ ParseParams({ { "linecount"s, true }, { "fill_with"s, false } }, params);
+ else
+ ParseParams({ { "slices"s, true }, { "fill_with"s, false } }, params);
+}
+
+InternalValue Slice::Filter(const InternalValue& baseVal, RenderContext& context)
+{
+ if (m_mode == BatchMode)
+ return Batch(baseVal, context);
+
+ InternalValue result;
+
+ bool isConverted = false;
+ ListAdapter list = ConvertToList(baseVal, isConverted);
+
+ if (!isConverted)
+ return result;
+
+ InternalValue sliceLengthValue = GetArgumentValue("slices", context);
+ int64_t sliceLength = ConvertToInt(sliceLengthValue);
+ InternalValue fillWith = GetArgumentValue("fill_with", context);
+
+ InternalValueList resultList;
+ InternalValueList sublist;
+ int sublistItemIndex = 0;
+ for (auto& item : list)
+ {
+ if (sublistItemIndex == 0)
+ sublist.clear();
+ if (sublistItemIndex == sliceLength)
+ {
+ resultList.push_back(ListAdapter::CreateAdapter(std::move(sublist)));
+ sublist.clear();
+ sublistItemIndex %= sliceLength;
+ }
+ sublist.push_back(item);
+ ++sublistItemIndex;
+ }
+ if (!IsEmpty(fillWith))
+ {
+ while (sublistItemIndex++ < sliceLength)
+ sublist.push_back(fillWith);
+ }
+ if (sublistItemIndex > 0)
+ resultList.push_back(ListAdapter::CreateAdapter(std::move(sublist)));
+
+ return InternalValue(ListAdapter::CreateAdapter(std::move(resultList)));
+}
+
+InternalValue Slice::Batch(const InternalValue& baseVal, RenderContext& context)
+{
+ auto linecount_value = ConvertToInt(GetArgumentValue("linecount", context));
+ InternalValue fillWith = GetArgumentValue("fill_with", context);
+
+ if (linecount_value <= 0)
+ return InternalValue();
+ auto linecount = static_cast<std::size_t>(linecount_value);
+
+ bool isConverted = false;
+ auto list = ConvertToList(baseVal, isConverted);
+ if (!isConverted)
+ return InternalValue();
+
+ auto elementsCount = list.GetSize().value_or(0);
+ if (!elementsCount)
+ return InternalValue();
+
+ InternalValueList resultList;
+ resultList.reserve(linecount);
+
+ const auto remainder = elementsCount % linecount;
+ const auto columns = elementsCount / linecount + (remainder > 0 ? 1 : 0);
+ for (std::size_t line = 0, idx = 0; line < linecount; ++line)
+ {
+ const auto elems = columns - (remainder && line >= remainder ? 1 : 0);
+ InternalValueList row;
+ row.reserve(columns);
+ std::fill_n(std::back_inserter(row), columns, fillWith);
+
+ for (std::size_t column = 0; column < elems; ++column)
+ row[column] = list.GetValueByIndex(idx++);
+
+ resultList.push_back(ListAdapter::CreateAdapter(std::move(row)));
+ }
+ return ListAdapter::CreateAdapter(std::move(resultList));
+}
+
+StringFormat::StringFormat(FilterParams params)
+{
+ ParseParams({}, params);
+ m_params.kwParams = std::move(m_args.extraKwArgs);
+ m_params.posParams = std::move(m_args.extraPosArgs);
+}
+
+Tester::Tester(FilterParams params, Tester::Mode mode)
+ : m_mode(mode)
+{
+ FilterParams newParams;
+
+ if ((mode == RejectMode || mode == SelectMode) && params.kwParams.empty() && params.posParams.empty())
+ {
+ m_noParams = true;
+ return;
+ }
+
+ if (mode == RejectMode || mode == SelectMode)
+ ParseParams({ { "tester", false } }, params);
+ else
+ ParseParams({ { "attribute", true }, { "tester", false } }, params);
+
+ m_testingParams.kwParams = std::move(m_args.extraKwArgs);
+ m_testingParams.posParams = std::move(m_args.extraPosArgs);
+}
+
+InternalValue Tester::Filter(const InternalValue& baseVal, RenderContext& context)
+{
+ InternalValue testerName = GetArgumentValue("tester", context);
+ InternalValue attrName = GetArgumentValue("attribute", context);
+
+ TesterPtr tester;
+
+ if (!IsEmpty(testerName))
+ {
+ tester = CreateTester(AsString(testerName), m_testingParams);
+
+ if (!tester)
+ return InternalValue();
+ }
+
+ bool isConverted = false;
+ auto list = ConvertToList(baseVal, isConverted);
+ if (!isConverted)
+ return InternalValue();
+
+ InternalValueList resultList;
+ resultList.reserve(list.GetSize().value_or(0));
+ std::copy_if(list.begin(), list.end(), std::back_inserter(resultList), [this, tester, attrName, &context](auto& val) {
+ InternalValue attrVal;
+ bool isAttr = !IsEmpty(attrName);
+ if (isAttr)
+ attrVal = Subscript(val, attrName, &context);
+
+ bool result = false;
+ if (tester)
+ result = tester->Test(isAttr ? attrVal : val, context);
+ else
+ result = ConvertToBool(isAttr ? attrVal : val);
+
+ return (m_mode == SelectMode || m_mode == SelectAttrMode) ? result : !result;
+ });
+
+ return ListAdapter::CreateAdapter(std::move(resultList));
+}
+
+ValueConverter::ValueConverter(FilterParams params, ValueConverter::Mode mode)
+ : m_mode(mode)
+{
+ switch (mode)
+ {
+ case ToFloatMode:
+ ParseParams({ { "default"s, false } }, params);
+ break;
+ case ToIntMode:
+ ParseParams({ { "default"s, false }, { "base"s, false, static_cast<int64_t>(10) } }, params);
+ break;
+ case ToListMode:
+ case AbsMode:
+ break;
+ case RoundMode:
+ ParseParams({ { "precision"s, false }, { "method"s, false, "common"s } }, params);
+ break;
+ }
+}
+
+struct ConverterParams
+{
+ ValueConverter::Mode mode;
+ InternalValue defValule;
+ InternalValue base;
+ InternalValue prec;
+ InternalValue roundMethod;
+};
+
+struct ValueConverterImpl : visitors::BaseVisitor<>
+{
+ using BaseVisitor::operator();
+
+ ValueConverterImpl(ConverterParams params)
+ : m_params(std::move(params))
+ {
+ }
+
+ InternalValue operator()(int64_t val) const
+ {
+ InternalValue result;
+ switch (m_params.mode)
+ {
+ case ValueConverter::ToFloatMode:
+ result = InternalValue(static_cast<double>(val));
+ break;
+ case ValueConverter::AbsMode:
+ result = InternalValue(static_cast<int64_t>(std::abs(val)));
+ break;
+ case ValueConverter::ToIntMode:
+ case ValueConverter::RoundMode:
+ result = InternalValue(static_cast<int64_t>(val));
+ break;
+ default:
+ break;
+ }
+
+ return result;
+ }
+
+ InternalValue operator()(double val) const
+ {
+ InternalValue result;
+ switch (m_params.mode)
+ {
+ case ValueConverter::ToFloatMode:
+ result = static_cast<double>(val);
+ break;
+ case ValueConverter::ToIntMode:
+ result = static_cast<int64_t>(val);
+ break;
+ case ValueConverter::AbsMode:
+ result = InternalValue(fabs(val));
+ break;
+ case ValueConverter::RoundMode:
+ {
+ auto method = AsString(m_params.roundMethod);
+ auto prec = GetAs<int64_t>(m_params.prec);
+ double pow10 = std::pow(10, static_cast<int>(prec));
+ val *= pow10;
+ if (method == "ceil")
+ val = val < 0 ? std::floor(val) : std::ceil(val);
+ else if (method == "floor")
+ val = val > 0 ? std::floor(val) : std::ceil(val);
+ else if (method == "common")
+ val = std::round(val);
+ result = InternalValue(val / pow10);
+ break;
+ }
+ default:
+ break;
+ }
+
+ return result;
+ }
+
+ static double ConvertToDouble(const char* buff, bool& isConverted)
+ {
+ char* endBuff = nullptr;
+ double dblVal = strtod(buff, &endBuff);
+ isConverted = *endBuff == 0;
+ return dblVal;
+ }
+
+ static double ConvertToDouble(const wchar_t* buff, bool& isConverted)
+ {
+ wchar_t* endBuff = nullptr;
+ double dblVal = wcstod(buff, &endBuff);
+ isConverted = *endBuff == 0;
+ return dblVal;
+ }
+
+ static long long ConvertToInt(const char* buff, int base, bool& isConverted)
+ {
+ char* endBuff = nullptr;
+ long long intVal = strtoll(buff, &endBuff, base);
+ isConverted = *endBuff == 0;
+ return intVal;
+ }
+
+ static long long ConvertToInt(const wchar_t* buff, int base, bool& isConverted)
+ {
+ wchar_t* endBuff = nullptr;
+ long long intVal = wcstoll(buff, &endBuff, base);
+ isConverted = *endBuff == 0;
+ return intVal;
+ }
+
+ template<typename CharT>
+ InternalValue operator()(const std::basic_string<CharT>& val) const
+ {
+ InternalValue result;
+ switch (m_params.mode)
+ {
+ case ValueConverter::ToFloatMode:
+ {
+ bool converted = false;
+ double dblVal = ConvertToDouble(val.c_str(), converted);
+
+ if (!converted)
+ result = m_params.defValule;
+ else
+ result = dblVal;
+ break;
+ }
+ case ValueConverter::ToIntMode:
+ {
+ int base = static_cast<int>(GetAs<int64_t>(m_params.base));
+ bool converted = false;
+ long long intVal = ConvertToInt(val.c_str(), base, converted);
+
+ if (!converted)
+ result = m_params.defValule;
+ else
+ result = static_cast<int64_t>(intVal);
+ break;
+ }
+ case ValueConverter::ToListMode:
+ result = ListAdapter::CreateAdapter(val.size(), [str = val](size_t idx) { return InternalValue(TargetString(str.substr(idx, 1))); });
+ default:
+ break;
+ }
+
+ return result;
+ }
+
+ template<typename CharT>
+ InternalValue operator()(const std::basic_string_view<CharT>& val) const
+ {
+ InternalValue result;
+ switch (m_params.mode)
+ {
+ case ValueConverter::ToFloatMode:
+ {
+ bool converted = false;
+ std::basic_string<CharT> str(val.begin(), val.end());
+ double dblVal = ConvertToDouble(str.c_str(), converted);
+
+ if (!converted)
+ result = m_params.defValule;
+ else
+ result = static_cast<double>(dblVal);
+ break;
+ }
+ case ValueConverter::ToIntMode:
+ {
+ int base = static_cast<int>(GetAs<int64_t>(m_params.base));
+ bool converted = false;
+ std::basic_string<CharT> str(val.begin(), val.end());
+ long long intVal = ConvertToInt(str.c_str(), base, converted);
+
+ if (!converted)
+ result = m_params.defValule;
+ else
+ result = static_cast<int64_t>(intVal);
+ break;
+ }
+ case ValueConverter::ToListMode:
+ result = ListAdapter::CreateAdapter(val.size(), [str = val](size_t idx) { return InternalValue(str.substr(idx, 1)); });
+ default:
+ break;
+ }
+
+ return result;
+ }
+
+ InternalValue operator()(const ListAdapter& val) const
+ {
+ if (m_params.mode == ValueConverter::ToListMode)
+ return InternalValue(val);
+
+ return InternalValue();
+ }
+
+ InternalValue operator()(const MapAdapter& val) const
+ {
+ if (m_params.mode != ValueConverter::ToListMode)
+ return InternalValue();
+
+ auto keys = val.GetKeys();
+ auto num_keys = keys.size();
+ return ListAdapter::CreateAdapter(num_keys, [values = std::move(keys)](size_t idx) { return InternalValue(values[idx]); });
+ }
+
+ template<typename T>
+ static T GetAs(const InternalValue& val, T defValue = 0)
+ {
+ ConverterParams params;
+ params.mode = ValueConverter::ToIntMode;
+ params.base = static_cast<int64_t>(10);
+ InternalValue intVal = Apply<ValueConverterImpl>(val, params);
+ const T* result = GetIf<int64_t>(&intVal);
+ if (result == nullptr)
+ return defValue;
+
+ return *result;
+ }
+
+ ConverterParams m_params;
+};
+
+InternalValue ValueConverter::Filter(const InternalValue& baseVal, RenderContext& context)
+{
+ ConverterParams params;
+ params.mode = m_mode;
+ params.defValule = GetArgumentValue("default", context);
+ params.base = GetArgumentValue("base", context);
+ params.prec = GetArgumentValue("precision", context);
+ params.roundMethod = GetArgumentValue("method", context);
+ auto result = Apply<ValueConverterImpl>(baseVal, params);
+ if (baseVal.ShouldExtendLifetime())
+ result.SetParentData(baseVal);
+
+ return result;
+}
+
+UserDefinedFilter::UserDefinedFilter(std::string filterName, FilterParams params)
+ : m_filterName(std::move(filterName))
+{
+ ParseParams({ { "*args" }, { "**kwargs" } }, params);
+ m_callParams.kwParams = m_args.extraKwArgs;
+ m_callParams.posParams = m_args.extraPosArgs;
+}
+
+InternalValue UserDefinedFilter::Filter(const InternalValue& baseVal, RenderContext& context)
+{
+ bool filterFound = false;
+ auto filterValPtr = context.FindValue(m_filterName, filterFound);
+ if (!filterFound)
+ throw std::runtime_error("Can't find filter '" + m_filterName + "'");
+
+ const Callable* callable = GetIf<Callable>(&filterValPtr->second);
+ if (callable == nullptr || callable->GetKind() != Callable::UserCallable)
+ return InternalValue();
+
+ CallParams tmpCallParams = helpers::EvaluateCallParams(m_callParams, context);
+ CallParams callParams;
+ callParams.kwParams = std::move(tmpCallParams.kwParams);
+ callParams.posParams.reserve(tmpCallParams.posParams.size() + 1);
+ callParams.posParams.push_back(baseVal);
+ for (auto& p : tmpCallParams.posParams)
+ callParams.posParams.push_back(std::move(p));
+
+ InternalValue result;
+ if (callable->GetType() != Callable::Type::Expression)
+ return InternalValue();
+
+ return callable->GetExpressionCallable()(callParams, context);
+}
+
+} // namespace filters
+} // namespace jinja2
diff --git a/contrib/libs/jinja2cpp/src/filters.h b/contrib/libs/jinja2cpp/src/filters.h
new file mode 100644
index 0000000000..1fdfc465c3
--- /dev/null
+++ b/contrib/libs/jinja2cpp/src/filters.h
@@ -0,0 +1,474 @@
+#ifndef JINJA2CPP_SRC_FILTERS_H
+#define JINJA2CPP_SRC_FILTERS_H
+
+#include "expression_evaluator.h"
+#include "function_base.h"
+#include "jinja2cpp/value.h"
+#include "render_context.h"
+
+#include <memory>
+#include <functional>
+
+namespace jinja2
+{
+using FilterPtr = std::shared_ptr<ExpressionFilter::IExpressionFilter>;
+using FilterParams = CallParamsInfo;
+
+extern FilterPtr CreateFilter(std::string filterName, CallParamsInfo params);
+
+namespace filters
+{
+class FilterBase : public FunctionBase, public ExpressionFilter::IExpressionFilter
+{
+};
+
+class ApplyMacro : public FilterBase
+{
+public:
+ ApplyMacro(FilterParams params);
+
+ InternalValue Filter(const InternalValue& baseVal, RenderContext& context) override;
+ bool IsEqual(const IComparable& other) const override
+ {
+ auto* value = dynamic_cast<const ApplyMacro*>(&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;
+};
+
+class Attribute : public FilterBase
+{
+public:
+ Attribute(FilterParams params);
+
+ InternalValue Filter(const InternalValue& baseVal, RenderContext& context) override;
+
+ bool IsEqual(const IComparable& other) const override
+ {
+ auto* value = dynamic_cast<const Attribute*>(&other);
+ if (!value)
+ return false;
+ if (m_args != value->m_args)
+ return false;
+ return true;
+ }
+};
+
+class Default : public FilterBase
+{
+public:
+ Default(FilterParams params);
+
+ InternalValue Filter(const InternalValue& baseVal, RenderContext& context) override;
+ bool IsEqual(const IComparable& other) const override
+ {
+ auto* value = dynamic_cast<const Default*>(&other);
+ if (!value)
+ return false;
+ if (m_args != value->m_args)
+ return false;
+ return true;
+ }
+};
+
+class DictSort : public FilterBase
+{
+public:
+ DictSort(FilterParams params);
+
+ InternalValue Filter(const InternalValue& baseVal, RenderContext& context) override;
+ bool IsEqual(const IComparable& other) const override
+ {
+ auto* value = dynamic_cast<const DictSort*>(&other);
+ if (!value)
+ return false;
+ if (m_args != value->m_args)
+ return false;
+ return true;
+ }
+};
+
+class GroupBy : public FilterBase
+{
+public:
+ GroupBy(FilterParams params);
+
+ InternalValue Filter(const InternalValue& baseVal, RenderContext& context) override;
+ bool IsEqual(const IComparable& other) const override
+ {
+ auto* value = dynamic_cast<const GroupBy*>(&other);
+ if (!value)
+ return false;
+ if (m_args != value->m_args)
+ return false;
+ return true;
+ }
+};
+
+class Join : public FilterBase
+{
+public:
+ Join(FilterParams params);
+
+ InternalValue Filter(const InternalValue& baseVal, RenderContext& context) override;
+ bool IsEqual(const IComparable& other) const override
+ {
+ auto* value = dynamic_cast<const Join*>(&other);
+ if (!value)
+ return false;
+ if (m_args != value->m_args)
+ return false;
+ return true;
+ }
+};
+
+class Map : public FilterBase
+{
+public:
+ Map(FilterParams params);
+
+ InternalValue Filter(const InternalValue& baseVal, RenderContext& context) override;
+ bool IsEqual(const IComparable& other) const override
+ {
+ auto* value = dynamic_cast<const Map*>(&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);
+
+ FilterParams m_mappingParams;
+};
+
+class PrettyPrint : public FilterBase
+{
+public:
+ PrettyPrint(FilterParams params);
+
+ InternalValue Filter(const InternalValue& baseVal, RenderContext& context) override;
+ bool IsEqual(const IComparable& other) const override
+ {
+ auto* value = dynamic_cast<const PrettyPrint*>(&other);
+ if (!value)
+ return false;
+ if (m_args != value->m_args)
+ return false;
+ return true;
+ }
+};
+
+class Random : public FilterBase
+{
+public:
+ Random(FilterParams params);
+
+ InternalValue Filter(const InternalValue& baseVal, RenderContext& context) override;
+ bool IsEqual(const IComparable& other) const override
+ {
+ auto* value = dynamic_cast<const Random*>(&other);
+ if (!value)
+ return false;
+ if (m_args != value->m_args)
+ return false;
+ return true;
+ }
+};
+
+class SequenceAccessor : public FilterBase
+{
+public:
+ enum Mode
+ {
+ FirstItemMode,
+ LastItemMode,
+ LengthMode,
+ MaxItemMode,
+ MinItemMode,
+ RandomMode,
+ ReverseMode,
+ SumItemsMode,
+ UniqueItemsMode,
+
+ };
+
+ SequenceAccessor(FilterParams params, Mode mode);
+
+ InternalValue Filter(const InternalValue& baseVal, RenderContext& context) override;
+
+ bool IsEqual(const IComparable& other) const override
+ {
+ auto* value = dynamic_cast<const SequenceAccessor*>(&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;
+};
+
+class Serialize : public FilterBase
+{
+public:
+ enum Mode
+ {
+ JsonMode,
+ XmlMode,
+ YamlMode
+ };
+
+ Serialize(FilterParams params, Mode mode);
+
+ InternalValue Filter(const InternalValue& baseVal, RenderContext& context) override;
+
+ bool IsEqual(const IComparable& other) const override
+ {
+ auto* value = dynamic_cast<const Serialize*>(&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;
+};
+
+class Slice : public FilterBase
+{
+public:
+ enum Mode
+ {
+ BatchMode,
+ SliceMode,
+ };
+
+ Slice(FilterParams params, Mode mode);
+
+ InternalValue Filter(const InternalValue& baseVal, RenderContext& context) override;
+ bool IsEqual(const IComparable& other) const override
+ {
+ auto* value = dynamic_cast<const Slice*>(&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);
+
+ Mode m_mode;
+};
+
+class Sort : public FilterBase
+{
+public:
+ Sort(FilterParams params);
+
+ InternalValue Filter(const InternalValue& baseVal, RenderContext& context) override;
+ bool IsEqual(const IComparable& other) const override
+ {
+ auto* value = dynamic_cast<const Sort*>(&other);
+ if (!value)
+ return false;
+ if (m_args != value->m_args)
+ return false;
+ return true;
+ }
+};
+
+class StringConverter : public FilterBase
+{
+public:
+ enum Mode
+ {
+ CapitalMode,
+ CamelMode,
+ EscapeCppMode,
+ EscapeHtmlMode,
+ LowerMode,
+ ReplaceMode,
+ StriptagsMode,
+ TitleMode,
+ TrimMode,
+ TruncateMode,
+ UpperMode,
+ WordCountMode,
+ WordWrapMode,
+ UnderscoreMode,
+ UrlEncodeMode,
+ CenterMode
+ };
+
+ StringConverter(FilterParams params, Mode mode);
+
+ InternalValue Filter(const InternalValue& baseVal, RenderContext& context) override;
+
+ bool IsEqual(const IComparable& other) const override
+ {
+ auto* value = dynamic_cast<const StringConverter*>(&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;
+};
+
+class StringFormat : public FilterBase
+{
+public:
+ StringFormat(FilterParams params);
+
+ InternalValue Filter(const InternalValue& baseVal, RenderContext& context) override;
+
+ bool IsEqual(const IComparable& other) const override
+ {
+ auto* value = dynamic_cast<const StringFormat*>(&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;
+};
+
+class Tester : public FilterBase
+{
+public:
+ enum Mode
+ {
+ RejectMode,
+ RejectAttrMode,
+ SelectMode,
+ SelectAttrMode,
+ };
+
+ Tester(FilterParams params, Mode mode);
+
+ InternalValue Filter(const InternalValue& baseVal, RenderContext& context) override;
+
+ bool IsEqual(const IComparable& other) const override
+ {
+ auto* value = dynamic_cast<const Tester*>(&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;
+ bool m_noParams = false;
+};
+
+class ValueConverter : public FilterBase
+{
+public:
+ enum Mode
+ {
+ ToFloatMode,
+ ToIntMode,
+ ToListMode,
+ AbsMode,
+ RoundMode,
+ };
+
+ ValueConverter(FilterParams params, Mode mode);
+
+ InternalValue Filter(const InternalValue& baseVal, RenderContext& context) override;
+
+ bool IsEqual(const IComparable& other) const override
+ {
+ auto* value = dynamic_cast<const ValueConverter*>(&other);
+ if (!value)
+ return false;
+ if (m_args != value->m_args)
+ return false;
+ return m_mode == value->m_mode;
+ }
+private:
+ Mode m_mode;
+};
+
+class XmlAttrFilter : public FilterBase
+{
+public:
+ explicit XmlAttrFilter(FilterParams params);
+
+ InternalValue Filter(const InternalValue& baseVal, RenderContext& context) override;
+ bool IsEqual(const IComparable& other) const override
+ {
+ auto* value = dynamic_cast<const XmlAttrFilter*>(&other);
+ if (!value)
+ return false;
+ if (m_args != value->m_args)
+ return false;
+ return true;
+ }
+};
+
+class UserDefinedFilter : public FilterBase
+{
+public:
+ UserDefinedFilter(std::string filterName, FilterParams params);
+
+ InternalValue Filter(const InternalValue& baseVal, RenderContext& context) override;
+
+ bool IsEqual(const IComparable& other) const override
+ {
+ auto* value = dynamic_cast<const UserDefinedFilter*>(&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;
+ FilterParams m_callParams;
+};
+
+} // namespace filters
+} // namespace jinja2
+
+#endif // JINJA2CPP_SRC_FILTERS_H
diff --git a/contrib/libs/jinja2cpp/src/function_base.h b/contrib/libs/jinja2cpp/src/function_base.h
new file mode 100644
index 0000000000..578545b031
--- /dev/null
+++ b/contrib/libs/jinja2cpp/src/function_base.h
@@ -0,0 +1,49 @@
+#ifndef JINJA2CPP_SRC_FUNCTION_BASE_H
+#define JINJA2CPP_SRC_FUNCTION_BASE_H
+
+#include "expression_evaluator.h"
+#include "internal_value.h"
+
+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<ArgumentInfo>& argsInfo, const CallParamsInfo& params);
+ InternalValue GetArgumentValue(const std::string& argName, RenderContext& context, InternalValue defVal = InternalValue());
+
+protected:
+ ParsedArgumentsInfo m_args;
+};
+
+//bool operator==(const FunctionBase& lhs, const FunctionBase& rhs)
+//{
+// return
+//}
+
+inline bool FunctionBase::ParseParams(const std::initializer_list<ArgumentInfo>& argsInfo, const CallParamsInfo& params)
+{
+ bool result = true;
+ m_args = helpers::ParseCallParamsInfo(argsInfo, params, result);
+
+ return result;
+}
+
+inline InternalValue FunctionBase::GetArgumentValue(const std::string& argName, RenderContext& context, InternalValue defVal)
+{
+ auto argExpr = m_args[argName];
+ return argExpr ? argExpr->Evaluate(context) : std::move(defVal);
+}
+
+} // namespace jinja2
+
+#endif // JINJA2CPP_SRC_FUNCTION_BASE_H
diff --git a/contrib/libs/jinja2cpp/src/generic_adapters.h b/contrib/libs/jinja2cpp/src/generic_adapters.h
new file mode 100644
index 0000000000..e6d0c9bcb0
--- /dev/null
+++ b/contrib/libs/jinja2cpp/src/generic_adapters.h
@@ -0,0 +1,215 @@
+#ifndef JINJA2CPP_SRC_GENERIC_ADAPTERS_H
+#define JINJA2CPP_SRC_GENERIC_ADAPTERS_H
+
+#include <jinja2cpp/value.h>
+#include "internal_value.h"
+
+namespace jinja2
+{
+
+template<typename ImplType, typename List, typename ValType, typename Base>
+class IndexedEnumeratorImpl : public Base
+{
+public:
+ using ValueType = ValType;
+ using ThisType = IndexedEnumeratorImpl<ImplType, List, ValType, Base>;
+
+ IndexedEnumeratorImpl(const List* list)
+ : m_list(list)
+ , m_maxItems(list->GetSize().value())
+ { }
+
+ void Reset() override
+ {
+ m_curItem = m_invalidIndex;
+ }
+
+ bool MoveNext() override
+ {
+ if (m_curItem == m_invalidIndex)
+ m_curItem = 0;
+ else
+ ++ m_curItem;
+
+ return m_curItem < m_maxItems;
+ }
+
+ bool IsEqual(const IComparable& other) const override
+ {
+ auto* val = dynamic_cast<const ThisType*>(&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<size_t>::max();
+ const List* m_list{};
+ size_t m_curItem = m_invalidIndex;
+ size_t m_maxItems{};
+};
+
+
+template<typename T>
+class IndexedListItemAccessorImpl : public IListItemAccessor, public IIndexBasedAccessor
+{
+public:
+ using ThisType = IndexedListItemAccessorImpl<T>;
+ class Enumerator : public IndexedEnumeratorImpl<Enumerator, ThisType, Value, IListEnumerator>
+ {
+ public:
+ using BaseClass = IndexedEnumeratorImpl<Enumerator, ThisType, Value, IListEnumerator>;
+#if defined(_MSC_VER)
+ using IndexedEnumeratorImpl::IndexedEnumeratorImpl;
+#else
+ using BaseClass::BaseClass;
+#endif
+
+ typename BaseClass::ValueType GetCurrent() const override
+ {
+ auto indexer = this->m_list->GetIndexer();
+ if (!indexer)
+ return Value();
+
+ return indexer->GetItemByIndex(this->m_curItem);
+ }
+ ListEnumeratorPtr Clone() const override
+ {
+ auto result = MakeEnumerator<Enumerator>(this->m_list);
+ auto base = static_cast<Enumerator*>(&(*result));
+ base->m_curItem = this->m_curItem;
+ return result;
+ }
+
+ ListEnumeratorPtr Move() override
+ {
+ auto result = MakeEnumerator<Enumerator>(this->m_list);
+ auto base = static_cast<Enumerator*>(&(*result));
+ base->m_curItem = this->m_curItem;
+ this->m_list = nullptr;
+ this->m_curItem = this->m_invalidIndex;
+ this->m_maxItems = 0;
+ return result;
+ }
+ };
+
+ Value GetItemByIndex(int64_t idx) const override
+ {
+ return IntValue2Value(std::move(static_cast<const T*>(this)->GetItem(idx).value()));
+ }
+
+ std::optional<size_t> GetSize() const override
+ {
+ return static_cast<const T*>(this)->GetItemsCountImpl();
+ }
+
+ const IIndexBasedAccessor* GetIndexer() const override
+ {
+ return this;
+ }
+
+ ListEnumeratorPtr CreateEnumerator() const override;
+
+ bool IsEqual(const IComparable& other) const override
+ {
+ auto* val = dynamic_cast<const ThisType*>(&other);
+ if (!val)
+ return false;
+ auto enumerator = CreateEnumerator();
+ auto otherEnum = val->CreateEnumerator();
+ if (enumerator && otherEnum && !enumerator->IsEqual(*otherEnum))
+ return false;
+ return true;
+ }
+};
+
+template<typename T>
+class IndexedListAccessorImpl : public IListAccessor, public IndexedListItemAccessorImpl<T>
+{
+public:
+ using ThisType = IndexedListAccessorImpl<T>;
+ class Enumerator : public IndexedEnumeratorImpl<Enumerator, ThisType, InternalValue, IListAccessorEnumerator>
+ {
+ public:
+ using BaseClass = IndexedEnumeratorImpl<Enumerator, ThisType, InternalValue, IListAccessorEnumerator>;
+#if defined(_MSC_VER)
+ using IndexedEnumeratorImpl::IndexedEnumeratorImpl;
+#else
+ using BaseClass::BaseClass;
+#endif
+
+ typename BaseClass::ValueType GetCurrent() const override
+ {
+ const auto& result = this->m_list->GetItem(this->m_curItem);
+ if (!result)
+ return InternalValue();
+
+ return result.value();
+ }
+
+ IListAccessorEnumerator* Clone() const override
+ {
+ auto result = new Enumerator(this->m_list);
+ auto base = result;
+ base->m_curItem = this->m_curItem;
+ return result;
+ }
+
+ IListAccessorEnumerator* Transfer() override
+ {
+ auto result = new Enumerator(std::move(*this));
+ auto base = result;
+ base->m_curItem = this->m_curItem;
+ this->m_list = nullptr;
+ this->m_curItem = this->m_invalidIndex;
+ this->m_maxItems = 0;
+ return result;
+ }
+ };
+
+ std::optional<size_t> GetSize() const override
+ {
+ return static_cast<const T*>(this)->GetItemsCountImpl();
+ }
+ ListAccessorEnumeratorPtr CreateListAccessorEnumerator() const override;
+};
+
+template<typename T>
+class MapItemAccessorImpl : public IMapItemAccessor
+{
+public:
+ Value GetValueByName(const std::string& name) const
+ {
+ return IntValue2Value(static_cast<const T*>(this)->GetItem(name));
+ }
+};
+
+template<typename T>
+class MapAccessorImpl : public IMapAccessor, public MapItemAccessorImpl<T>
+{
+public:
+};
+
+template<typename T>
+inline ListAccessorEnumeratorPtr IndexedListAccessorImpl<T>::CreateListAccessorEnumerator() const
+{
+ return ListAccessorEnumeratorPtr(new Enumerator(this));
+}
+
+template<typename T>
+inline ListEnumeratorPtr IndexedListItemAccessorImpl<T>::CreateEnumerator() const
+{
+ return MakeEnumerator<Enumerator>(this);
+}
+
+} // namespace jinja2
+
+#endif // JINJA2CPP_SRC_GENERIC_ADAPTERS_H
diff --git a/contrib/libs/jinja2cpp/src/generic_list.cpp b/contrib/libs/jinja2cpp/src/generic_list.cpp
new file mode 100644
index 0000000000..883ef3615a
--- /dev/null
+++ b/contrib/libs/jinja2cpp/src/generic_list.cpp
@@ -0,0 +1,38 @@
+#include <jinja2cpp/generic_list.h>
+#include <jinja2cpp/generic_list_iterator.h>
+
+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/contrib/libs/jinja2cpp/src/helpers.h b/contrib/libs/jinja2cpp/src/helpers.h
new file mode 100644
index 0000000000..74e87400df
--- /dev/null
+++ b/contrib/libs/jinja2cpp/src/helpers.h
@@ -0,0 +1,119 @@
+#ifndef JINJA2CPP_SRC_HELPERS_H
+#define JINJA2CPP_SRC_HELPERS_H
+
+#include <string_view>
+#include <jinja2cpp/string_helpers.h>
+
+#include <string>
+#include <type_traits>
+#include <cwchar>
+
+namespace jinja2
+{
+struct MultiStringLiteral
+{
+ const char* charValue;
+ const wchar_t* wcharValue;
+
+ constexpr MultiStringLiteral(const char* val, const wchar_t* wval)
+ : charValue(val)
+ , wcharValue(wval)
+ {
+ }
+
+ template<typename CharT>
+ constexpr auto GetValue() const
+ {
+#if __cplusplus < 202002L
+ return GetValueStr<CharT>();
+#else
+ constexpr auto memPtr = SelectMemberPtr<CharT, &MultiStringLiteral::charValue, &MultiStringLiteral::wcharValue>::GetPtr();
+ return std::basic_string_view<CharT>(this->*memPtr);
+#endif
+ }
+
+ template<typename CharT>
+ constexpr auto GetValueStr() const
+ {
+ constexpr auto memPtr = SelectMemberPtr<CharT, &MultiStringLiteral::charValue, &MultiStringLiteral::wcharValue>::GetPtr();
+ return std::basic_string<CharT>(this->*memPtr);
+ }
+
+ template<typename CharT, const char* MultiStringLiteral::*, const wchar_t* MultiStringLiteral::*>
+ struct SelectMemberPtr;
+
+ template<const char* (MultiStringLiteral::*charMemPtr), const wchar_t* (MultiStringLiteral::*wcharMemPtr)>
+ struct SelectMemberPtr<char, charMemPtr, wcharMemPtr>
+ {
+ static constexpr auto GetPtr() {return charMemPtr;}
+ };
+
+ template<const char* (MultiStringLiteral::*charMemPtr), const wchar_t* (MultiStringLiteral::*wcharMemPtr)>
+ struct SelectMemberPtr<wchar_t, charMemPtr, wcharMemPtr>
+ {
+ static constexpr auto GetPtr() {return wcharMemPtr;}
+ };
+
+ template<typename CharT>
+ friend std::basic_ostream<CharT>& operator << (std::basic_ostream<CharT>& os, const MultiStringLiteral& lit)
+ {
+ os << lit.GetValue<CharT>();
+ return os;
+ }
+};
+
+#define UNIVERSAL_STR(Str) \
+ ::jinja2::MultiStringLiteral { Str, L##Str }
+
+//! CompileEscapes replaces escape characters by their meanings.
+/**
+ * @param[in] s Characters sequence with zero or more escape characters.
+ * @return Characters sequence copy where replaced all escape characters by
+ * their meanings.
+ */
+template<typename Sequence>
+Sequence CompileEscapes(Sequence s)
+{
+ auto itr1 = s.begin();
+ auto itr2 = s.begin();
+ const auto end = s.cend();
+
+ auto removalCount = 0;
+
+ while (end != itr1)
+ {
+ if ('\\' == *itr1)
+ {
+ ++removalCount;
+
+ if (end == ++itr1)
+ break;
+ if ('\\' != *itr1)
+ {
+ switch (*itr1)
+ {
+ case 'n': *itr1 = '\n'; break;
+ case 'r': *itr1 = '\r'; break;
+ case 't': *itr1 = '\t'; break;
+ default: break;
+ }
+
+ continue;
+ }
+ }
+
+ if (itr1 != itr2)
+ *itr2 = *itr1;
+
+ ++itr1;
+ ++itr2;
+ }
+
+ s.resize(s.size() - removalCount);
+
+ return s;
+}
+
+} // namespace jinja2
+
+#endif // JINJA2CPP_SRC_HELPERS_H
diff --git a/contrib/libs/jinja2cpp/src/internal_value.cpp b/contrib/libs/jinja2cpp/src/internal_value.cpp
new file mode 100644
index 0000000000..7683bc2c97
--- /dev/null
+++ b/contrib/libs/jinja2cpp/src/internal_value.cpp
@@ -0,0 +1,1053 @@
+#include "internal_value.h"
+
+#include "expression_evaluator.h"
+#include "generic_adapters.h"
+#include "helpers.h"
+#include "value_visitors.h"
+
+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<UserCallable>& lhs, const types::ValuePtr<UserCallable>& rhs)
+{
+ if (lhs && rhs)
+ return *lhs == *rhs;
+ if ((lhs && !rhs) || (!lhs && rhs))
+ return false;
+ return true;
+}
+
+bool operator!=(const types::ValuePtr<UserCallable>& lhs, const types::ValuePtr<UserCallable>& rhs)
+{
+ return !(lhs == rhs);
+}
+
+bool operator==(const types::ValuePtr<ValuesMap>& lhs, const types::ValuePtr<ValuesMap>& rhs)
+{
+ if (lhs && rhs)
+ return *lhs == *rhs;
+ if ((lhs && !rhs) || (!lhs && rhs))
+ return false;
+ return true;
+}
+
+bool operator!=(const types::ValuePtr<ValuesMap>& lhs, const types::ValuePtr<ValuesMap>& rhs)
+{
+ return !(lhs == rhs);
+}
+
+bool operator==(const types::ValuePtr<Value>& lhs, const types::ValuePtr<Value>& rhs)
+{
+ if (lhs && rhs)
+ return *lhs == *rhs;
+ if ((lhs && !rhs) || (!lhs && rhs))
+ return false;
+ return true;
+}
+
+bool operator!=(const types::ValuePtr<Value>& lhs, const types::ValuePtr<Value>& rhs)
+{
+ return !(lhs == rhs);
+}
+
+bool operator==(const types::ValuePtr<std::vector<Value>>& lhs, const types::ValuePtr<std::vector<Value>>& rhs)
+{
+ if (lhs && rhs)
+ return *lhs == *rhs;
+ if ((lhs && !rhs) || (!lhs && rhs))
+ return false;
+ return true;
+}
+
+bool operator!=(const types::ValuePtr<std::vector<Value>>& lhs, const types::ValuePtr<std::vector<Value>>& 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);
+
+struct SubscriptionVisitor : public visitors::BaseVisitor<>
+{
+ using BaseVisitor<>::operator();
+
+ template<typename CharT>
+ InternalValue operator()(const MapAdapter& values, const std::basic_string<CharT>& fieldName) const
+ {
+ auto field = ConvertString<std::string>(fieldName);
+ if (!values.HasValue(field))
+ return InternalValue();
+
+ return values.GetValueByName(field);
+ }
+
+ template<typename CharT>
+ InternalValue operator()(const MapAdapter& values, const std::basic_string_view<CharT>& fieldName) const
+ {
+ auto field = ConvertString<std::string>(fieldName);
+ if (!values.HasValue(field))
+ return InternalValue();
+
+ return values.GetValueByName(field);
+ }
+
+ template<typename CharT>
+ InternalValue operator()(std::basic_string<CharT> value, const std::basic_string<CharT>& /*fieldName*/) const
+ {
+ return TargetString(std::move(value));
+ }
+
+ InternalValue operator()(const ListAdapter& values, int64_t index) const
+ {
+ if (index < 0 || static_cast<size_t>(index) >= values.GetSize())
+ return InternalValue();
+
+ return values.GetValueByIndex(index);
+ }
+
+ InternalValue operator()(const MapAdapter& /*values*/, int64_t /*index*/) const { return InternalValue(); }
+
+ template<typename CharT>
+ InternalValue operator()(const std::basic_string<CharT>& str, int64_t index) const
+ {
+ if (index < 0 || static_cast<size_t>(index) >= str.size())
+ return InternalValue();
+
+ std::basic_string<CharT> resultStr(1, str[static_cast<size_t>(index)]);
+ return TargetString(std::move(resultStr));
+ }
+
+ template<typename CharT>
+ InternalValue operator()(const std::basic_string_view<CharT>& str, int64_t index) const
+ {
+ // std::cout << "operator() (const std::basic_string<CharT>& str, int64_t index)" << ": index = " << index << std::endl;
+ if (index < 0 || static_cast<size_t>(index) >= str.size())
+ return InternalValue();
+
+ std::basic_string<CharT> result(1, str[static_cast<size_t>(index)]);
+ return TargetString(std::move(result));
+ }
+
+ template<typename CharT>
+ InternalValue operator()(const KeyValuePair& values, const std::basic_string<CharT>& fieldName) const
+ {
+ return SubscriptKvPair(values, ConvertString<std::string>(fieldName));
+ }
+
+ template<typename CharT>
+ InternalValue operator()(const KeyValuePair& values, const std::basic_string_view<CharT>& fieldName) const
+ {
+ return SubscriptKvPair(values, ConvertString<std::string>(fieldName));
+ }
+
+ InternalValue SubscriptKvPair(const KeyValuePair& values, const std::string& field) const
+ {
+ // std::cout << "operator() (const KeyValuePair& values, const std::string& field)" << ": field = " << field << std::endl;
+ if (field == "key")
+ return InternalValue(values.key);
+ else if (field == "value")
+ return values.value;
+
+ return InternalValue();
+ }
+};
+
+InternalValue Subscript(const InternalValue& val, const InternalValue& subscript, RenderContext* values)
+{
+ static const std::string callOperName = "value()";
+ auto result = Apply2<SubscriptionVisitor>(val, subscript);
+
+ if (!values)
+ return result;
+
+ auto map = GetIf<MapAdapter>(&result);
+ if (!map || !map->HasValue(callOperName))
+ return result;
+
+ auto callableVal = map->GetValueByName(callOperName);
+ auto callable = GetIf<Callable>(&callableVal);
+ if (!callable || callable->GetKind() == Callable::Macro || callable->GetType() == Callable::Type::Statement)
+ return result;
+
+ CallParams callParams;
+ return callable->GetExpressionCallable()(callParams, *values);
+}
+
+InternalValue Subscript(const InternalValue& val, const std::string& subscript, RenderContext* values)
+{
+ return Subscript(val, InternalValue(subscript), values);
+}
+
+struct StringGetter : public visitors::BaseVisitor<std::string>
+{
+ using BaseVisitor::operator();
+
+ std::string operator()(const std::string& str) const { return str; }
+ std::string operator()(const std::string_view& str) const { return std::string(str.begin(), str.end()); }
+ std::string operator()(const std::wstring& str) const { return ConvertString<std::string>(str); }
+ std::string operator()(const std::wstring_view& str) const { return ConvertString<std::string>(str); }
+};
+
+std::string AsString(const InternalValue& val)
+{
+ return Apply<StringGetter>(val);
+}
+
+struct ListConverter : public visitors::BaseVisitor<boost::optional<ListAdapter>>
+{
+ using BaseVisitor::operator();
+
+ using result_t = boost::optional<ListAdapter>;
+
+ bool strictConvertion;
+
+ ListConverter(bool strict)
+ : strictConvertion(strict)
+ {
+ }
+
+ result_t operator()(const ListAdapter& list) const { return list; }
+ result_t operator()(const MapAdapter& map) const
+ {
+ if (strictConvertion)
+ return result_t();
+
+ InternalValueList list;
+ for (auto& k : map.GetKeys())
+ list.push_back(TargetString(k));
+
+ return ListAdapter::CreateAdapter(std::move(list));
+ }
+
+ template<typename CharT>
+ result_t operator() (const std::basic_string<CharT>& str) const
+ {
+ return strictConvertion ? result_t() : result_t(ListAdapter::CreateAdapter(str.size(), [str](size_t idx) {
+ return TargetString(str.substr(idx, 1));}));
+ }
+
+ template<typename CharT>
+ result_t operator()(const std::basic_string_view<CharT>& str) const
+ {
+ return strictConvertion ? result_t() : result_t(ListAdapter::CreateAdapter(str.size(), [str](size_t idx) {
+ return TargetString(std::basic_string<CharT>(str[idx], 1)); }));
+ }
+};
+
+ListAdapter ConvertToList(const InternalValue& val, bool& isConverted, bool strictConversion)
+{
+ auto result = Apply<ListConverter>(val, strictConversion);
+ if (!result)
+ {
+ isConverted = false;
+ return ListAdapter();
+ }
+ isConverted = true;
+ return result.get();
+}
+
+ListAdapter ConvertToList(const InternalValue& val, InternalValue subscipt, bool& isConverted, bool strictConversion)
+{
+ auto result = Apply<ListConverter>(val, strictConversion);
+ if (!result)
+ {
+ isConverted = false;
+ return ListAdapter();
+ }
+ isConverted = true;
+
+ if (IsEmpty(subscipt))
+ return std::move(result.get());
+
+ return result.get().ToSubscriptedList(subscipt, false);
+}
+
+template<typename T>
+class ByRef
+{
+public:
+ explicit ByRef(const T& val)
+ : m_val(&val)
+ {
+ }
+
+ const T& Get() const { return *m_val; }
+ T& Get() { return *const_cast<T*>(m_val); }
+ bool ShouldExtendLifetime() const { return false; }
+ bool operator==(const ByRef<T>& 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<T>& other) const
+ {
+ return !(*this == other);
+ }
+private:
+ const T* m_val{};
+};
+
+template<typename T>
+class ByVal
+{
+public:
+ explicit ByVal(T&& val)
+ : m_val(std::move(val))
+ {
+ }
+ ~ByVal() = default;
+
+ const T& Get() const { return m_val; }
+ T& Get() { return m_val; }
+ bool ShouldExtendLifetime() const { return false; }
+ bool operator==(const ByVal<T>& other) const
+ {
+ return m_val == other.m_val;
+ }
+ bool operator!=(const ByVal<T>& other) const
+ {
+ return !(*this == other);
+ }
+private:
+ T m_val;
+};
+
+template<typename T>
+class BySharedVal
+{
+public:
+ explicit BySharedVal(T&& val)
+ : m_val(std::make_shared<T>(std::move(val)))
+ {
+ }
+ ~BySharedVal() = default;
+
+ const T& Get() const { return *m_val; }
+ T& Get() { return *m_val; }
+ bool ShouldExtendLifetime() const { return true; }
+
+ bool operator==(const BySharedVal<T>& other) const
+ {
+ return m_val == other.m_val;
+ }
+ bool operator!=(const BySharedVal<T>& other) const
+ {
+ return !(*this == other);
+ }
+private:
+ std::shared_ptr<T> m_val;
+};
+
+template<template<typename> class Holder>
+class GenericListAdapter : public IListAccessor
+{
+public:
+ struct Enumerator : public IListAccessorEnumerator
+ {
+ ListEnumeratorPtr m_enum;
+
+ explicit Enumerator(ListEnumeratorPtr e)
+ : m_enum(std::move(e))
+ {
+ }
+
+ // Inherited via IListAccessorEnumerator
+ void Reset() override
+ {
+ if (m_enum)
+ m_enum->Reset();
+ }
+ bool MoveNext() override { return !m_enum ? false : m_enum->MoveNext(); }
+ 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<const Enumerator*>(&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<typename U>
+ GenericListAdapter(U&& values)
+ : m_values(std::forward<U>(values))
+ {
+ }
+
+ std::optional<size_t> GetSize() const override { return m_values.Get().GetSize(); }
+ std::optional<InternalValue> GetItem(int64_t idx) const override
+ {
+ const IListItemAccessor* accessor = m_values.Get().GetAccessor();
+ auto indexer = accessor->GetIndexer();
+ if (!indexer)
+ return std::optional<InternalValue>();
+
+ auto val = indexer->GetItemByIndex(idx);
+ return visit(visitors::InputValueConvertor(true, false), std::move(val.data())).get();
+ }
+ bool ShouldExtendLifetime() const override { return m_values.ShouldExtendLifetime(); }
+ ListAccessorEnumeratorPtr CreateListAccessorEnumerator() const override
+ {
+ const IListItemAccessor* accessor = m_values.Get().GetAccessor();
+ if (!accessor)
+ return ListAccessorEnumeratorPtr(new Enumerator(MakeEmptyListEnumeratorPtr()));
+ return ListAccessorEnumeratorPtr(new Enumerator(m_values.Get().GetAccessor()->CreateEnumerator()));
+ }
+ GenericList CreateGenericList() const override
+ {
+ // return m_values.Get();
+ return GenericList([list = m_values]() -> const IListItemAccessor* { return list.Get().GetAccessor(); });
+ }
+
+private:
+ Holder<GenericList> m_values;
+};
+
+template<template<typename> class Holder>
+class ValuesListAdapter : public IndexedListAccessorImpl<ValuesListAdapter<Holder>>
+{
+public:
+ template<typename U>
+ ValuesListAdapter(U&& values)
+ : m_values(std::forward<U>(values))
+ {
+ }
+
+ size_t GetItemsCountImpl() const { return m_values.Get().size(); }
+ std::optional<InternalValue> GetItem(int64_t idx) const override
+ {
+ const auto& val = m_values.Get()[static_cast<size_t>(idx)];
+ return visit(visitors::InputValueConvertor(false, true), val.data()).get();
+ }
+ bool ShouldExtendLifetime() const override { return m_values.ShouldExtendLifetime(); }
+ GenericList CreateGenericList() const override
+ {
+ // return m_values.Get();
+ return GenericList([list = *this]() -> const IListItemAccessor* { return &list; });
+ }
+
+private:
+ Holder<ValuesList> m_values;
+};
+
+ListAdapter ListAdapter::CreateAdapter(InternalValueList&& values)
+{
+ class Adapter : public IndexedListAccessorImpl<Adapter>
+ {
+ public:
+ explicit Adapter(InternalValueList&& values)
+ : m_values(std::move(values))
+ {
+ }
+
+ size_t GetItemsCountImpl() const { return m_values.size(); }
+ std::optional<InternalValue> GetItem(int64_t idx) const override { return m_values[static_cast<size_t>(idx)]; }
+ bool ShouldExtendLifetime() const override { return false; }
+ GenericList CreateGenericList() const override
+ {
+ return GenericList([adapter = *this]() -> const IListItemAccessor* { return &adapter; });
+ }
+
+ private:
+ InternalValueList m_values;
+ };
+
+ return ListAdapter([accessor = Adapter(std::move(values))]() { return &accessor; });
+}
+
+ListAdapter ListAdapter::CreateAdapter(const GenericList& values)
+{
+ return ListAdapter([accessor = GenericListAdapter<ByRef>(values)]() { return &accessor; });
+}
+
+ListAdapter ListAdapter::CreateAdapter(const ValuesList& values)
+{
+ return ListAdapter([accessor = ValuesListAdapter<ByRef>(values)]() { return &accessor; });
+}
+
+ListAdapter ListAdapter::CreateAdapter(GenericList&& values)
+{
+ return ListAdapter([accessor = GenericListAdapter<BySharedVal>(std::move(values))]() { return &accessor; });
+}
+
+ListAdapter ListAdapter::CreateAdapter(ValuesList&& values)
+{
+ return ListAdapter([accessor = ValuesListAdapter<BySharedVal>(std::move(values))]() { return &accessor; });
+}
+
+ListAdapter ListAdapter::CreateAdapter(std::function<std::optional<InternalValue>()> fn)
+{
+ using GenFn = std::function<std::optional<InternalValue>()>;
+
+ class Adapter : public IListAccessor
+ {
+ public:
+ class Enumerator : public IListAccessorEnumerator
+ {
+ public:
+ explicit Enumerator(const GenFn* fn)
+ : m_fn(fn)
+ {
+ if (!fn)
+ throw std::runtime_error("List enumerator couldn't be created without element accessor function!");
+ }
+
+ void Reset() override {}
+
+ bool MoveNext() override
+ {
+ if (m_isFinished)
+ return false;
+
+ auto res = (*m_fn)();
+ if (!res)
+ return false;
+
+ m_current = *res;
+
+ return true;
+ }
+
+ InternalValue GetCurrent() const override { return m_current; }
+
+ IListAccessorEnumerator* Clone() const override
+ {
+ auto result = new Enumerator(*this);
+ return result;
+ }
+
+ IListAccessorEnumerator* Transfer() override
+ {
+ auto result = new Enumerator(std::move(*this));
+ return result;
+ }
+
+ bool IsEqual(const IComparable& other) const override
+ {
+ auto* val = dynamic_cast<const Enumerator*>(&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{};
+ InternalValue m_current;
+ bool m_isFinished = false;
+ };
+
+ explicit Adapter(std::function<std::optional<InternalValue>()>&& fn)
+ : m_fn(std::move(fn))
+ {
+ }
+
+ std::optional<size_t> GetSize() const override { return std::optional<size_t>(); }
+ std::optional<InternalValue> GetItem(int64_t /*idx*/) const override { return std::optional<InternalValue>(); }
+ bool ShouldExtendLifetime() const override { return false; }
+ ListAccessorEnumeratorPtr CreateListAccessorEnumerator() const override { return ListAccessorEnumeratorPtr(new Enumerator(&m_fn)); }
+
+ GenericList CreateGenericList() const override
+ {
+ return GenericList(); // return GenericList([adapter = *this]() -> const ListItemAccessor* {return &adapter; });
+ }
+
+ private:
+ std::function<std::optional<InternalValue>()> m_fn;
+ };
+
+ return ListAdapter([accessor = Adapter(std::move(fn))]() { return &accessor; });
+}
+
+ListAdapter ListAdapter::CreateAdapter(size_t listSize, std::function<InternalValue(size_t idx)> fn)
+{
+ using GenFn = std::function<InternalValue(size_t idx)>;
+
+ class Adapter : public IndexedListAccessorImpl<Adapter>
+ {
+ public:
+ explicit Adapter(size_t listSize, GenFn&& fn)
+ : m_listSize(listSize)
+ , m_fn(std::move(fn))
+ {
+ }
+
+ size_t GetItemsCountImpl() const { return m_listSize; }
+ std::optional<InternalValue> GetItem(int64_t idx) const override { return m_fn(static_cast<size_t>(idx)); }
+ bool ShouldExtendLifetime() const override { return false; }
+ GenericList CreateGenericList() const override
+ {
+ return GenericList([adapter = *this]() -> const IListItemAccessor* { return &adapter; });
+ }
+
+ private:
+ size_t m_listSize;
+ GenFn m_fn;
+ };
+
+ return ListAdapter([accessor = Adapter(listSize, std::move(fn))]() { return &accessor; });
+}
+
+template<typename Holder>
+auto CreateIndexedSubscribedList(Holder&& holder, const InternalValue& subscript, size_t size)
+{
+ return ListAdapter::CreateAdapter(
+ size, [h = std::forward<Holder>(holder), subscript](size_t idx) -> InternalValue { return Subscript(h.Get().GetValueByIndex(idx), subscript, nullptr); });
+}
+
+template<typename Holder>
+auto CreateGenericSubscribedList(Holder&& holder, const InternalValue& subscript)
+{
+ return ListAdapter::CreateAdapter([h = std::forward<Holder>(holder), e = ListAccessorEnumeratorPtr(), isFirst = true, isLast = false, subscript]() mutable {
+ using ResultType = std::optional<InternalValue>;
+ if (isFirst)
+ {
+ e = h.Get().GetEnumerator();
+ isLast = !e->MoveNext();
+ isFirst = false;
+ }
+ if (isLast)
+ return ResultType();
+
+ return ResultType(Subscript(e->GetCurrent(), subscript, nullptr));
+ });
+}
+
+ListAdapter ListAdapter::ToSubscriptedList(const InternalValue& subscript, bool asRef) const
+{
+ auto listSize = GetSize();
+ if (asRef)
+ {
+ ByRef<ListAdapter> holder(*this);
+ return listSize ? CreateIndexedSubscribedList(holder, subscript, *listSize) : CreateGenericSubscribedList(holder, subscript);
+ }
+ else
+ {
+ ListAdapter tmp(*this);
+ BySharedVal<ListAdapter> holder(std::move(tmp));
+ return listSize ? CreateIndexedSubscribedList(std::move(holder), subscript, *listSize) : CreateGenericSubscribedList(std::move(holder), subscript);
+ }
+}
+
+InternalValueList ListAdapter::ToValueList() const
+{
+ InternalValueList result;
+ std::copy(begin(), end(), std::back_inserter(result));
+ return result;
+}
+
+template<template<typename> class Holder, bool CanModify>
+class InternalValueMapAdapter : public MapAccessorImpl<InternalValueMapAdapter<Holder, CanModify>>
+{
+public:
+ template<typename U>
+ InternalValueMapAdapter(U&& values)
+ : m_values(std::forward<U>(values))
+ {
+ }
+
+ size_t GetSize() const override { return m_values.Get().size(); }
+ bool HasValue(const std::string& name) const override { return m_values.Get().count(name) != 0; }
+ InternalValue GetItem(const std::string& name) const override
+ {
+ auto& vals = m_values.Get();
+ auto p = vals.find(name);
+ if (p == vals.end())
+ return InternalValue();
+
+ return p->second;
+ }
+ std::vector<std::string> GetKeys() const override
+ {
+ std::vector<std::string> result;
+
+ for (auto& i : m_values.Get())
+ result.push_back(i.first);
+
+ return result;
+ }
+
+ bool SetValue(std::string name, const InternalValue& val) override
+ {
+ if (CanModify)
+ {
+ m_values.Get()[name] = val;
+ return true;
+ }
+ return false;
+ }
+ bool ShouldExtendLifetime() const override { return m_values.ShouldExtendLifetime(); }
+ GenericMap CreateGenericMap() const override
+ {
+ return GenericMap([accessor = *this]() -> const IMapItemAccessor* { return &accessor; });
+ }
+ bool IsEqual(const IComparable& other) const override
+ {
+ auto* val = dynamic_cast<const InternalValueMapAdapter*>(&other);
+ if (!val)
+ return false;
+ return m_values == val->m_values;
+ }
+private:
+ Holder<InternalValueMap> m_values;
+};
+
+InternalValue Value2IntValue(const Value& val)
+{
+ auto result = std::visit(visitors::InputValueConvertor(false, true), val.data());
+ if (result)
+ return result.get();
+
+ return InternalValue(ValueRef(val));
+}
+
+InternalValue Value2IntValue(Value&& val)
+{
+ auto result = std::visit(visitors::InputValueConvertor(true, false), val.data());
+ if (result)
+ return result.get();
+
+ return InternalValue(ValueRef(val));
+}
+
+template<template<typename> class Holder>
+class GenericMapAdapter : public MapAccessorImpl<GenericMapAdapter<Holder>>
+{
+public:
+ template<typename U>
+ GenericMapAdapter(U&& values)
+ : m_values(std::forward<U>(values))
+ {
+ }
+
+ size_t GetSize() const override { return m_values.Get().GetSize(); }
+ bool HasValue(const std::string& name) const override { return m_values.Get().HasValue(name); }
+ InternalValue GetItem(const std::string& name) const override
+ {
+ auto val = m_values.Get().GetValueByName(name);
+ if (val.isEmpty())
+ return InternalValue();
+
+ return Value2IntValue(std::move(val));
+ }
+ std::vector<std::string> GetKeys() const override { return m_values.Get().GetKeys(); }
+ bool ShouldExtendLifetime() const override { return m_values.ShouldExtendLifetime(); }
+ GenericMap CreateGenericMap() const override
+ {
+ return GenericMap([accessor = *this]() -> const IMapItemAccessor* { return accessor.m_values.Get().GetAccessor(); });
+ }
+ bool IsEqual(const IComparable& other) const override
+ {
+ auto* val = dynamic_cast<const GenericMapAdapter*>(&other);
+ if (!val)
+ return false;
+ return m_values == val->m_values;
+ }
+private:
+ Holder<GenericMap> m_values;
+};
+
+template<template<typename> class Holder>
+class ValuesMapAdapter : public MapAccessorImpl<ValuesMapAdapter<Holder>>
+{
+public:
+ template<typename U>
+ ValuesMapAdapter(U&& values)
+ : m_values(std::forward<U>(values))
+ {
+ }
+
+ size_t GetSize() const override { return m_values.Get().size(); }
+ bool HasValue(const std::string& name) const override { return m_values.Get().count(name) != 0; }
+ InternalValue GetItem(const std::string& name) const override
+ {
+ auto& vals = m_values.Get();
+ auto p = vals.find(name);
+ if (p == vals.end())
+ return InternalValue();
+
+ return Value2IntValue(p->second);
+ }
+ std::vector<std::string> GetKeys() const override
+ {
+ std::vector<std::string> result;
+
+ for (auto& i : m_values.Get())
+ result.push_back(i.first);
+
+ return result;
+ }
+ bool ShouldExtendLifetime() const override { return m_values.ShouldExtendLifetime(); }
+ GenericMap CreateGenericMap() const override
+ {
+ return GenericMap([accessor = *this]() -> const IMapItemAccessor* { return &accessor; });
+ }
+ bool IsEqual(const IComparable& other) const override
+ {
+ auto* val = dynamic_cast<const ValuesMapAdapter*>(&other);
+ if (!val)
+ return false;
+ return m_values == val->m_values;
+ }
+private:
+ Holder<ValuesMap> m_values;
+};
+
+MapAdapter CreateMapAdapter(InternalValueMap&& values)
+{
+ return MapAdapter([accessor = InternalValueMapAdapter<ByVal, true>(std::move(values))]() mutable { return &accessor; });
+}
+
+MapAdapter CreateMapAdapter(const InternalValueMap* values)
+{
+ return MapAdapter([accessor = InternalValueMapAdapter<ByRef, false>(*values)]() mutable { return &accessor; });
+}
+
+MapAdapter CreateMapAdapter(const GenericMap& values)
+{
+ return MapAdapter([accessor = GenericMapAdapter<ByRef>(values)]() mutable { return &accessor; });
+}
+
+MapAdapter CreateMapAdapter(GenericMap&& values)
+{
+ return MapAdapter([accessor = GenericMapAdapter<BySharedVal>(std::move(values))]() mutable { return &accessor; });
+}
+
+MapAdapter CreateMapAdapter(const ValuesMap& values)
+{
+ return MapAdapter([accessor = ValuesMapAdapter<ByRef>(values)]() mutable { return &accessor; });
+}
+
+MapAdapter CreateMapAdapter(ValuesMap&& values)
+{
+ return MapAdapter([accessor = ValuesMapAdapter<BySharedVal>(std::move(values))]() mutable { return &accessor; });
+}
+
+struct OutputValueConvertor
+{
+ using result_t = Value;
+
+ result_t operator()(const EmptyValue&) const { return result_t(); }
+ result_t operator()(const MapAdapter& adapter) const { return result_t(adapter.CreateGenericMap()); }
+ result_t operator()(const ListAdapter& adapter) const { return result_t(adapter.CreateGenericList()); }
+ result_t operator()(const ValueRef& ref) const { return ref.get(); }
+ result_t operator()(const TargetString& str) const
+ {
+ switch (str.index())
+ {
+ case 0:
+ return std::get<std::string>(str);
+ default:
+ return std::get<std::wstring>(str);
+ }
+ }
+ result_t operator()(const TargetStringView& str) const
+ {
+ switch (str.index())
+ {
+ case 0:
+ return std::get<std::string_view>(str);
+ default:
+ return std::get<std::wstring_view>(str);
+ }
+ }
+ 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<IRendererBase>&) const { return result_t(); }
+
+ template<typename T>
+ result_t operator()(const RecWrapper<T>& val) const
+ {
+ return this->operator()(const_cast<const T&>(*val));
+ }
+
+ template<typename T>
+ result_t operator()(RecWrapper<T>& val) const
+ {
+ return this->operator()(*val);
+ }
+
+ template<typename T>
+ result_t operator()(T&& val) const
+ {
+ return result_t(std::forward<T>(val));
+ }
+
+ bool m_byValue;
+};
+
+Value OptIntValue2Value(std::optional<InternalValue> val)
+{
+ if (val)
+ return Apply<OutputValueConvertor>(val.value());
+
+ return Value();
+}
+
+Value IntValue2Value(const InternalValue& val)
+{
+ return Apply<OutputValueConvertor>(val);
+}
+
+class ContextMapper : public IMapItemAccessor
+{
+public:
+ explicit ContextMapper(RenderContext* context)
+ : m_context(context)
+ {
+ }
+
+ size_t GetSize() const override { return std::numeric_limits<size_t>::max(); }
+ bool HasValue(const std::string& name) const override
+ {
+ bool found = false;
+ m_context->FindValue(name, found);
+ return found;
+ }
+ Value GetValueByName(const std::string& name) const override
+ {
+ bool found = false;
+ auto p = m_context->FindValue(name, found);
+ return found ? IntValue2Value(p->second) : Value();
+ }
+ std::vector<std::string> GetKeys() const override { return std::vector<std::string>(); }
+
+ bool IsEqual(const IComparable& other) const override
+ {
+ auto* val = dynamic_cast<const ContextMapper*>(&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;
+};
+
+UserCallableParams PrepareUserCallableParams(const CallParams& params, RenderContext& context, const std::vector<ArgumentInfo>& argsInfo)
+{
+ UserCallableParams result;
+
+ ParsedArguments args = helpers::ParseCallParams(argsInfo, params, result.paramsParsed);
+ if (!result.paramsParsed)
+ return result;
+
+ for (auto& argInfo : argsInfo)
+ {
+ if (argInfo.name.size() > 1 && argInfo.name[0] == '*')
+ continue;
+
+ auto p = args.args.find(argInfo.name);
+ if (p == args.args.end())
+ {
+ result.args[argInfo.name] = IntValue2Value(argInfo.defaultVal);
+ continue;
+ }
+
+ const auto& v = p->second;
+ result.args[argInfo.name] = IntValue2Value(v);
+ }
+
+ ValuesMap 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)
+ extraPosArgs.push_back(IntValue2Value(p));
+ result.extraPosArgs = Value(std::move(extraPosArgs));
+ result.context = GenericMap([accessor = ContextMapper(&context)]() -> const IMapItemAccessor* { return &accessor; });
+
+ return result;
+}
+
+namespace visitors
+{
+
+InputValueConvertor::result_t InputValueConvertor::ConvertUserCallable(const UserCallable& val)
+{
+ std::vector<ArgumentInfo> args;
+ for (auto& pi : val.argsInfo)
+ {
+ args.emplace_back(pi.paramName, pi.isMandatory, Value2IntValue(pi.defValue));
+ }
+
+ return InternalValue(Callable(Callable::UserCallable, [val, argsInfo = std::move(args)](const CallParams& params, RenderContext& context) -> InternalValue {
+ auto ucParams = PrepareUserCallableParams(params, context, argsInfo);
+ return Value2IntValue(val.callable(ucParams));
+ }));
+}
+
+} // namespace visitors
+
+} // namespace jinja2
diff --git a/contrib/libs/jinja2cpp/src/internal_value.h b/contrib/libs/jinja2cpp/src/internal_value.h
new file mode 100644
index 0000000000..22a933f257
--- /dev/null
+++ b/contrib/libs/jinja2cpp/src/internal_value.h
@@ -0,0 +1,673 @@
+#ifndef JINJA2CPP_SRC_INTERNAL_VALUE_H
+#define JINJA2CPP_SRC_INTERNAL_VALUE_H
+
+#include <jinja2cpp/value.h>
+//#include <jinja2cpp/value_ptr.h>
+
+#include <boost/iterator/iterator_facade.hpp>
+#include <boost/variant/recursive_wrapper.hpp>
+#include <boost/unordered_map.hpp>
+
+#include <fmt/core.h>
+
+#if defined(_MSC_VER) && _MSC_VER <= 1900 // robin_hood hash map doesn't compatible with MSVC 14.0
+#include <unordered_map>
+#else
+#include "robin_hood.h"
+#endif
+
+
+#include <string_view>
+#include <variant>
+
+#include <functional>
+
+namespace jinja2
+{
+
+template <class T>
+class ReferenceWrapper
+{
+public:
+ using type = T;
+
+ ReferenceWrapper(T& ref) noexcept
+ : m_ptr(std::addressof(ref))
+ {
+ }
+
+ ReferenceWrapper(T&&) = delete;
+ ReferenceWrapper(const ReferenceWrapper&) noexcept = default;
+
+ // assignment
+ ReferenceWrapper& operator=(const ReferenceWrapper& x) noexcept = default;
+
+ // access
+ T& get() const noexcept
+ {
+ return *m_ptr;
+ }
+
+private:
+ T* m_ptr;
+};
+
+template<typename T, size_t SizeHint = 48>
+class RecursiveWrapper
+{
+public:
+ RecursiveWrapper() = default;
+
+ RecursiveWrapper(const T& value)
+ : m_data(value)
+ {}
+
+ RecursiveWrapper(T&& value)
+ : m_data(std::move(value))
+ {}
+
+ const T& GetValue() const {return m_data.get();}
+ T& GetValue() {return m_data.get();}
+
+private:
+ boost::recursive_wrapper<T> m_data;
+
+#if 0
+ enum class State
+ {
+ Undefined,
+ Inplace,
+ Ptr
+ };
+
+ State m_state;
+
+ union
+ {
+ uint64_t dummy;
+ nonstd::value_ptr<T> ptr;
+ } m_data;
+#endif
+};
+
+template<typename T>
+auto MakeWrapped(T&& val)
+{
+ return RecursiveWrapper<std::decay_t<T>>(std::forward<T>(val));
+}
+
+using ValueRef = ReferenceWrapper<const Value>;
+using TargetString = std::variant<std::string, std::wstring>;
+using TargetStringView = std::variant<std::string_view, std::wstring_view>;
+
+class ListAdapter;
+class MapAdapter;
+class RenderContext;
+class OutStream;
+class Callable;
+struct CallParams;
+struct KeyValuePair;
+class IRendererBase;
+
+class InternalValue;
+using InternalValueData = std::variant<
+ EmptyValue,
+ bool,
+ std::string,
+ TargetString,
+ TargetStringView,
+ int64_t,
+ double,
+ ValueRef,
+ ListAdapter,
+ MapAdapter,
+ RecursiveWrapper<KeyValuePair>,
+ RecursiveWrapper<Callable>,
+ std::shared_ptr<IRendererBase>>;
+
+
+using InternalValueRef = ReferenceWrapper<InternalValue>;
+using InternalValueList = std::vector<InternalValue>;
+
+template<typename T, bool isRecursive = false>
+struct ValueGetter
+{
+ template<typename V>
+ static auto& Get(V&& val)
+ {
+ return std::get<T>(std::forward<V>(val).GetData());
+ }
+
+ static auto GetPtr(const InternalValue* val);
+
+ static auto GetPtr(InternalValue* val);
+
+ template<typename V>
+ static auto GetPtr(V* val, std::enable_if_t<!std::is_same<V, InternalValue>::value>* = nullptr)
+ {
+ return std::get_if<T>(val);
+ }
+};
+
+template<typename T>
+struct ValueGetter<T, true>
+{
+ template<typename V>
+ static auto& Get(V&& val)
+ {
+ auto& ref = std::get<RecursiveWrapper<T>>(std::forward<V>(val));
+ return ref.GetValue();
+ }
+
+ static auto GetPtr(const InternalValue* val);
+
+ static auto GetPtr(InternalValue* val);
+
+ template<typename V>
+ static auto GetPtr(V* val, std::enable_if_t<!std::is_same<V, InternalValue>::value>* = nullptr)
+ {
+ auto ref = std::get_if<RecursiveWrapper<T>>(val);
+ return !ref ? nullptr : &ref->GetValue();
+ }
+};
+
+template<typename T>
+struct IsRecursive : std::false_type {};
+
+template<>
+struct IsRecursive<KeyValuePair> : std::true_type {};
+
+template<>
+struct IsRecursive<Callable> : std::true_type {};
+
+struct IListAccessorEnumerator : virtual IComparable
+{
+ virtual ~IListAccessorEnumerator() {}
+
+ virtual void Reset() = 0;
+
+ virtual bool MoveNext() = 0;
+ virtual InternalValue GetCurrent() const = 0;
+
+ virtual IListAccessorEnumerator* Clone() const = 0;
+ virtual IListAccessorEnumerator* Transfer() = 0;
+/*
+ struct Cloner
+ {
+ Cloner() = default;
+
+ IListAccessorEnumerator* operator()(const IListAccessorEnumerator &x) const
+ {
+ return x.Clone();
+ }
+
+ IListAccessorEnumerator* operator()(IListAccessorEnumerator &&x) const
+ {
+ return x.Transfer();
+ }
+ };
+ */
+};
+
+using ListAccessorEnumeratorPtr = types::ValuePtr<IListAccessorEnumerator>;
+
+struct IListAccessor
+{
+ virtual ~IListAccessor() {}
+
+ virtual std::optional<size_t> GetSize() const = 0;
+ virtual std::optional<InternalValue> GetItem(int64_t idx) const = 0;
+ virtual ListAccessorEnumeratorPtr CreateListAccessorEnumerator() const = 0;
+ virtual GenericList CreateGenericList() const = 0;
+ virtual bool ShouldExtendLifetime() const = 0;
+};
+
+
+using ListAccessorProvider = std::function<const IListAccessor*()>;
+
+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;
+ virtual std::vector<std::string> GetKeys() const = 0;
+ virtual bool SetValue(std::string, const InternalValue&) {return false;}
+ virtual GenericMap CreateGenericMap() const = 0;
+ virtual bool ShouldExtendLifetime() const = 0;
+};
+
+using MapAccessorProvider = std::function<IMapAccessor*()>;
+
+class ListAdapter
+{
+public:
+ ListAdapter() {}
+ explicit ListAdapter(ListAccessorProvider prov) : m_accessorProvider(std::move(prov)) {}
+ ListAdapter(const ListAdapter&) = default;
+ ListAdapter(ListAdapter&&) = default;
+
+ static ListAdapter CreateAdapter(InternalValueList&& values);
+ static ListAdapter CreateAdapter(const GenericList& values);
+ static ListAdapter CreateAdapter(const ValuesList& values);
+ static ListAdapter CreateAdapter(GenericList&& values);
+ static ListAdapter CreateAdapter(ValuesList&& values);
+ static ListAdapter CreateAdapter(std::function<std::optional<InternalValue> ()> fn);
+ static ListAdapter CreateAdapter(size_t listSize, std::function<InternalValue (size_t idx)> fn);
+
+ ListAdapter& operator = (const ListAdapter&) = default;
+ ListAdapter& operator = (ListAdapter&&) = default;
+
+ std::optional<size_t> GetSize() const
+ {
+ if (m_accessorProvider && m_accessorProvider())
+ {
+ return m_accessorProvider()->GetSize();
+ }
+
+ return 0;
+ }
+ InternalValue GetValueByIndex(int64_t idx) const;
+ bool ShouldExtendLifetime() const
+ {
+ if (m_accessorProvider && m_accessorProvider())
+ {
+ return m_accessorProvider()->ShouldExtendLifetime();
+ }
+
+ return false;
+ }
+
+ ListAdapter ToSubscriptedList(const InternalValue& subscript, bool asRef = false) const;
+ InternalValueList ToValueList() const;
+ GenericList CreateGenericList() const
+ {
+ if (m_accessorProvider && m_accessorProvider())
+ return m_accessorProvider()->CreateGenericList();
+
+ return GenericList();
+ }
+ ListAccessorEnumeratorPtr GetEnumerator() const;
+
+ class Iterator;
+
+ Iterator begin() const;
+ Iterator end() const;
+
+private:
+ ListAccessorProvider m_accessorProvider;
+};
+
+class MapAdapter
+{
+public:
+ MapAdapter() = default;
+ explicit MapAdapter(MapAccessorProvider prov) : m_accessorProvider(std::move(prov)) {}
+
+ size_t GetSize() const
+ {
+ if (m_accessorProvider && m_accessorProvider())
+ {
+ return m_accessorProvider()->GetSize();
+ }
+
+ return 0;
+ }
+ // InternalValue GetValueByIndex(int64_t idx) const;
+ bool HasValue(const std::string& name) const
+ {
+ if (m_accessorProvider && m_accessorProvider())
+ {
+ return m_accessorProvider()->HasValue(name);
+ }
+
+ return false;
+ }
+ InternalValue GetValueByName(const std::string& name) const;
+ std::vector<std::string> GetKeys() const
+ {
+ if (m_accessorProvider && m_accessorProvider())
+ {
+ return m_accessorProvider()->GetKeys();
+ }
+
+ return std::vector<std::string>();
+ }
+ bool SetValue(std::string name, const InternalValue& val)
+ {
+ if (m_accessorProvider && m_accessorProvider())
+ {
+ return m_accessorProvider()->SetValue(std::move(name), val);
+ }
+
+ return false;
+ }
+ bool ShouldExtendLifetime() const
+ {
+ if (m_accessorProvider && m_accessorProvider())
+ {
+ return m_accessorProvider()->ShouldExtendLifetime();
+ }
+
+ return false;
+ }
+
+ GenericMap CreateGenericMap() const
+ {
+ if (m_accessorProvider && m_accessorProvider())
+ return m_accessorProvider()->CreateGenericMap();
+
+ return GenericMap();
+ }
+
+private:
+ MapAccessorProvider m_accessorProvider;
+};
+
+
+class InternalValue
+{
+public:
+ InternalValue() = default;
+
+ template<typename T>
+ InternalValue(T&& val, typename std::enable_if<!std::is_same<std::decay_t<T>, InternalValue>::value>::type* = nullptr)
+ : m_data(InternalValueData(std::forward<T>(val)))
+ {
+ }
+
+ 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();
+ }
+
+ void SetParentData(InternalValue&& val)
+ {
+ m_parentData = std::move(val.GetData());
+ }
+
+ bool ShouldExtendLifetime() const
+ {
+ if (m_parentData.index() != 0)
+ return true;
+
+ const MapAdapter* ma = std::get_if<MapAdapter>(&m_data);
+ if (ma != nullptr)
+ return ma->ShouldExtendLifetime();
+
+ const ListAdapter* la = std::get_if<ListAdapter>(&m_data);
+ if (la != nullptr)
+ return la->ShouldExtendLifetime();
+
+ return false;
+ }
+
+ 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,
+ const InternalValue,
+ boost::forward_traversal_tag>
+{
+public:
+ Iterator() = default;
+
+ explicit Iterator(ListAccessorEnumeratorPtr&& iter)
+ : m_iterator(std::move(iter))
+ , m_isFinished(!m_iterator->MoveNext())
+ , m_currentVal(m_isFinished ? InternalValue() : m_iterator->GetCurrent())
+ {}
+
+private:
+ friend class boost::iterator_core_access;
+
+ void increment()
+ {
+ m_isFinished = !m_iterator->MoveNext();
+ ++ m_currentIndex;
+ m_currentVal = m_isFinished ? InternalValue() : m_iterator->GetCurrent();
+ }
+
+ bool equal(const Iterator& other) const
+ {
+ if (!this->m_iterator)
+ return !other.m_iterator ? true : other.equal(*this);
+
+ if (!other.m_iterator)
+ return this->m_isFinished;
+// 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
+ {
+ return m_currentVal;
+ }
+
+ ListAccessorEnumeratorPtr m_iterator;
+ bool m_isFinished = true;
+ mutable uint64_t m_currentIndex = 0;
+ mutable InternalValue m_currentVal;
+};
+
+#if defined(_MSC_VER) && _MSC_VER <= 1900 // robin_hood hash map doesn't compatible with MSVC 14.0
+typedef std::unordered_map<std::string, InternalValue> InternalValueMap;
+#else
+typedef robin_hood::unordered_map<std::string, InternalValue> InternalValueMap;
+#endif
+
+
+MapAdapter CreateMapAdapter(InternalValueMap&& values);
+MapAdapter CreateMapAdapter(const InternalValueMap* values);
+MapAdapter CreateMapAdapter(const GenericMap& values);
+MapAdapter CreateMapAdapter(GenericMap&& values);
+MapAdapter CreateMapAdapter(const ValuesMap& values);
+MapAdapter CreateMapAdapter(ValuesMap&& values);
+
+template<typename T, bool V>
+inline auto ValueGetter<T, V>::GetPtr(const InternalValue* val)
+{
+ return std::get_if<T>(&val->GetData());
+}
+
+template<typename T, bool V>
+inline auto ValueGetter<T, V>::GetPtr(InternalValue* val)
+{
+ return std::get_if<T>(&val->GetData());
+}
+
+template<typename T>
+inline auto ValueGetter<T, true>::GetPtr(const InternalValue* val)
+{
+ auto ref = std::get_if<RecursiveWrapper<T>>(&val->GetData());
+ return !ref ? nullptr : &ref->GetValue();
+}
+
+template<typename T>
+inline auto ValueGetter<T, true>::GetPtr(InternalValue* val)
+{
+ auto ref = std::get_if<RecursiveWrapper<T>>(&val->GetData());
+ return !ref ? nullptr : &ref->GetValue();
+}
+
+template<typename T, typename V>
+auto& Get(V&& val)
+{
+ return ValueGetter<T, IsRecursive<T>::value>::Get(std::forward<V>(val).GetData());
+}
+
+template<typename T, typename V>
+auto GetIf(V* val)
+{
+ return ValueGetter<T, IsRecursive<T>::value>::GetPtr(val);
+}
+
+
+inline InternalValue ListAdapter::GetValueByIndex(int64_t idx) const
+{
+ if (m_accessorProvider && m_accessorProvider())
+ {
+ const auto& val = m_accessorProvider()->GetItem(idx);
+ if (val)
+ return std::move(val.value());
+
+ return InternalValue();
+ }
+
+ return InternalValue();
+}
+
+//inline InternalValue MapAdapter::GetValueByIndex(int64_t idx) const
+//{
+// if (m_accessorProvider && m_accessorProvider())
+// {
+// return static_cast<const IListAccessor*>(m_accessorProvider())->GetItem(idx);
+// }
+
+// return InternalValue();
+//}
+
+inline InternalValue MapAdapter::GetValueByName(const std::string& name) const
+{
+ if (m_accessorProvider && m_accessorProvider())
+ {
+ return m_accessorProvider()->GetItem(name);
+ }
+
+ return InternalValue();
+}
+
+inline ListAccessorEnumeratorPtr ListAdapter::GetEnumerator() const {return m_accessorProvider()->CreateListAccessorEnumerator();}
+inline ListAdapter::Iterator ListAdapter::begin() const {return Iterator(m_accessorProvider()->CreateListAccessorEnumerator());}
+inline ListAdapter::Iterator ListAdapter::end() const {return Iterator();}
+
+
+struct KeyValuePair
+{
+ std::string key;
+ InternalValue value;
+};
+
+
+class Callable
+{
+public:
+ enum Kind
+ {
+ GlobalFunc,
+ SpecialFunc,
+ Macro,
+ UserCallable
+ };
+ using ExpressionCallable = std::function<InternalValue (const CallParams&, RenderContext&)>;
+ using StatementCallable = std::function<void (const CallParams&, OutStream&, RenderContext&)>;
+
+ using CallableHolder = std::variant<ExpressionCallable, StatementCallable>;
+
+ enum class Type
+ {
+ Expression,
+ Statement
+ };
+
+ Callable(Kind kind, ExpressionCallable&& callable)
+ : m_kind(kind)
+ , m_callable(std::move(callable))
+ {
+ }
+
+ Callable(Kind kind, StatementCallable&& callable)
+ : m_kind(kind)
+ , m_callable(std::move(callable))
+ {
+ }
+
+ auto GetType() const
+ {
+ return m_callable.index() == 0 ? Type::Expression : Type::Statement;
+ }
+
+ auto GetKind() const
+ {
+ return m_kind;
+ }
+
+ auto& GetCallable() const
+ {
+ return m_callable;
+ }
+
+ auto& GetExpressionCallable() const
+ {
+ return std::get<ExpressionCallable>(m_callable);
+ }
+
+ auto& GetStatementCallable() const
+ {
+ return std::get<StatementCallable>(m_callable);
+ }
+
+private:
+ Kind m_kind;
+ CallableHolder m_callable;
+};
+
+inline bool IsEmpty(const InternalValue& val)
+{
+ return val.IsEmpty() || std::get_if<EmptyValue>(&val.GetData()) != nullptr;
+}
+
+class RenderContext;
+
+template<typename Fn>
+auto MakeDynamicProperty(Fn&& fn)
+{
+ return CreateMapAdapter(InternalValueMap{
+ {"value()", Callable(Callable::GlobalFunc, std::forward<Fn>(fn))}
+ });
+}
+
+template<typename CharT>
+auto sv_to_string(const std::basic_string_view<CharT>& sv)
+{
+ return std::basic_string<CharT>(sv.begin(), sv.end());
+}
+
+InternalValue Subscript(const InternalValue& val, const InternalValue& subscript, RenderContext* values);
+InternalValue Subscript(const InternalValue& val, const std::string& subscript, RenderContext* values);
+std::string AsString(const InternalValue& val);
+ListAdapter ConvertToList(const InternalValue& val, bool& isConverted, bool strictConversion = true);
+ListAdapter ConvertToList(const InternalValue& val, InternalValue subscipt, bool& isConverted, bool strictConversion = true);
+Value IntValue2Value(const InternalValue& val);
+Value OptIntValue2Value(std::optional<InternalValue> val);
+
+} // namespace jinja2
+
+#endif // JINJA2CPP_SRC_INTERNAL_VALUE_H
diff --git a/contrib/libs/jinja2cpp/src/lexer.cpp b/contrib/libs/jinja2cpp/src/lexer.cpp
new file mode 100644
index 0000000000..dfd33eb37e
--- /dev/null
+++ b/contrib/libs/jinja2cpp/src/lexer.cpp
@@ -0,0 +1,122 @@
+#include "lexer.h"
+
+#include <iostream>
+
+namespace jinja2
+{
+
+bool Lexer::Preprocess()
+{
+ bool result = true;
+ while (1)
+ {
+ lexertk::token token = m_tokenizer();
+ if (token.is_error())
+ {
+ result = false;
+ break;
+ }
+
+ Token newToken;
+ newToken.range.startOffset = token.position;
+ newToken.range.endOffset = newToken.range.startOffset + token.length;
+
+ if (token.type == lexertk::token::e_eof)
+ {
+ newToken.type = Token::Eof;
+ m_tokens.push_back(std::move(newToken));
+ break;
+ }
+
+ switch (token.type)
+ {
+ case lexertk::token::e_number:
+ result = ProcessNumber(token, newToken);
+ break;
+ case lexertk::token::e_symbol:
+ result = ProcessSymbolOrKeyword(token, newToken);
+ break;
+ case lexertk::token::e_string:
+ result = ProcessString(token, newToken);
+ break;
+ case lexertk::token::e_lte:
+ newToken.type = Token::LessEqual;
+ break;
+ case lexertk::token::e_ne:
+ newToken.type = Token::NotEqual;
+ break;
+ case lexertk::token::e_gte:
+ newToken.type = Token::GreaterEqual;
+ break;
+ case lexertk::token::e_eq:
+ newToken.type = Token::Equal;
+ break;
+ case lexertk::token::e_mulmul:
+ newToken.type = Token::MulMul;
+ break;
+ case lexertk::token::e_divdiv:
+ newToken.type = Token::DivDiv;
+ break;
+ default:
+ newToken.type = static_cast<Token::Type>(token.type);
+ break;
+ }
+
+ if (result)
+ m_tokens.push_back(std::move(newToken));
+ else
+ break;
+ }
+
+ return result;
+}
+
+bool Lexer::ProcessNumber(const lexertk::token&, Token& newToken)
+{
+ newToken.type = Token::FloatNum;
+ newToken.value = m_helper->GetAsValue(newToken.range, newToken.type);
+ return true;
+}
+
+bool Lexer::ProcessSymbolOrKeyword(const lexertk::token&, Token& newToken)
+{
+ Keyword kwType = m_helper->GetKeyword(newToken.range);
+ Token::Type tokType = Token::Unknown;
+
+ switch (kwType)
+ {
+ case Keyword::None:
+ tokType = Token::None;
+ break;
+ case Keyword::True:
+ tokType = Token::True;
+ break;
+ case Keyword::False:
+ tokType = Token::False;
+ break;
+ default:
+ tokType = Token::Unknown;
+ break;
+ }
+
+ if (tokType == Token::Unknown)
+ {
+ newToken.type = Token::Identifier;
+ auto id = m_helper->GetAsString(newToken.range);
+ newToken.value = InternalValue(id);
+ }
+ else
+ {
+ newToken.type = tokType;
+ }
+ return true;
+}
+
+bool Lexer::ProcessString(const lexertk::token&, Token& newToken)
+{
+ newToken.type = Token::String;
+ newToken.value = m_helper->GetAsValue(newToken.range, newToken.type);
+ return true;
+}
+
+} // namespace jinja2
diff --git a/contrib/libs/jinja2cpp/src/lexer.h b/contrib/libs/jinja2cpp/src/lexer.h
new file mode 100644
index 0000000000..e644de2f7b
--- /dev/null
+++ b/contrib/libs/jinja2cpp/src/lexer.h
@@ -0,0 +1,375 @@
+#ifndef JINJA2CPP_SRC_LEXER_H
+#define JINJA2CPP_SRC_LEXER_H
+
+#include "lexertk.h"
+#include "internal_value.h"
+
+#include <functional>
+
+namespace jinja2
+{
+struct CharRange
+{
+ size_t startOffset;
+ size_t endOffset;
+ auto size() const {return endOffset - startOffset;}
+};
+
+struct Token
+{
+ enum Type
+ {
+ Unknown,
+
+ // One-symbol operators
+ Lt = '<',
+ Gt = '>',
+ Plus = '+',
+ Minus = '-',
+ Percent = '%',
+ Mul = '*',
+ Div = '/',
+ LBracket = '(',
+ RBracket = ')',
+ LSqBracket = '[',
+ RSqBracket = ']',
+ LCrlBracket = '{',
+ RCrlBracket = '}',
+ Assign = '=',
+ Comma = ',',
+ Eof = 256,
+
+ // General
+ Identifier,
+ IntegerNum,
+ FloatNum,
+ String,
+
+ // Operators
+ Equal,
+ NotEqual,
+ LessEqual,
+ GreaterEqual,
+ StarStar,
+ DashDash,
+ MulMul,
+ DivDiv,
+ True,
+ False,
+ None,
+
+ // Keywords
+ LogicalOr,
+ LogicalAnd,
+ LogicalNot,
+ In,
+ Is,
+ For,
+ Endfor,
+ If,
+ Else,
+ ElIf,
+ EndIf,
+ Block,
+ EndBlock,
+ Extends,
+ Macro,
+ EndMacro,
+ Call,
+ EndCall,
+ Filter,
+ EndFilter,
+ Set,
+ EndSet,
+ Include,
+ Import,
+ Recursive,
+ Scoped,
+ With,
+ EndWith,
+ Without,
+ Ignore,
+ Missing,
+ Context,
+ From,
+ As,
+ Do,
+
+ // Template control
+ CommentBegin,
+ CommentEnd,
+ RawBegin,
+ RawEnd,
+ MetaBegin,
+ MetaEnd,
+ StmtBegin,
+ StmtEnd,
+ ExprBegin,
+ ExprEnd,
+ };
+
+ Type type = Unknown;
+ CharRange range = {0, 0};
+ InternalValue value;
+
+ bool IsEof() const
+ {
+ return type == Eof;
+ }
+
+ bool operator == (char ch) const
+ {
+ return type == ch;
+ }
+
+ bool operator == (Type t) const
+ {
+ return type == t;
+ }
+
+ template<typename T>
+ bool operator != (T v) const
+ {
+ return !(*this == v);
+ }
+};
+
+enum class Keyword
+{
+ Unknown,
+
+ // Keywords
+ LogicalOr,
+ LogicalAnd,
+ LogicalNot,
+ True,
+ False,
+ None,
+ In,
+ Is,
+ For,
+ Endfor,
+ If,
+ Else,
+ ElIf,
+ EndIf,
+ Block,
+ EndBlock,
+ Extends,
+ Macro,
+ EndMacro,
+ Call,
+ EndCall,
+ Filter,
+ EndFilter,
+ Set,
+ EndSet,
+ Include,
+ Import,
+ Recursive,
+ Scoped,
+ With,
+ EndWith,
+ Without,
+ Ignore,
+ Missing,
+ Context,
+ From,
+ As,
+ Do,
+};
+
+struct LexerHelper
+{
+ virtual std::string GetAsString(const CharRange& range) = 0;
+ virtual InternalValue GetAsValue(const CharRange& range, Token::Type type) = 0;
+ virtual Keyword GetKeyword(const CharRange& range) = 0;
+ virtual char GetCharAt(size_t pos) = 0;
+};
+
+class Lexer
+{
+public:
+ using TokensList = std::vector<Token>;
+ Lexer(std::function<lexertk::token ()> tokenizer, LexerHelper* helper)
+ : m_tokenizer(std::move(tokenizer))
+ , m_helper(helper)
+ {
+ }
+
+ bool Preprocess();
+ const TokensList& GetTokens() const
+ {
+ return m_tokens;
+ }
+
+ auto GetHelper() const {return m_helper;}
+
+private:
+ bool ProcessNumber(const lexertk::token& token, Token& newToken);
+ bool ProcessSymbolOrKeyword(const lexertk::token& token, Token& newToken);
+ bool ProcessString(const lexertk::token& token, Token& newToken);
+private:
+ std::function<lexertk::token ()> m_tokenizer;
+ TokensList m_tokens;
+ LexerHelper* m_helper;
+};
+
+class LexScanner
+{
+public:
+ struct State
+ {
+ Lexer::TokensList::const_iterator m_begin;
+ Lexer::TokensList::const_iterator m_end;
+ Lexer::TokensList::const_iterator m_cur;
+ };
+
+ struct StateSaver
+ {
+ StateSaver(LexScanner& scanner)
+ : m_state(scanner.m_state)
+ , m_scanner(scanner)
+ {
+ }
+
+ ~StateSaver()
+ {
+ if (!m_commited)
+ m_scanner.m_state = m_state;
+ }
+
+ void Commit()
+ {
+ m_commited = true;
+ }
+
+ State m_state;
+ LexScanner& m_scanner;
+ bool m_commited = false;
+ };
+
+ LexScanner(const Lexer& lexer)
+ : m_helper(lexer.GetHelper())
+ {
+ m_state.m_begin = lexer.GetTokens().begin();
+ m_state.m_end = lexer.GetTokens().end();
+ Reset();
+ }
+
+ void Reset()
+ {
+ m_state.m_cur = m_state.m_begin;
+ }
+
+ auto GetState() const
+ {
+ return m_state;
+ }
+
+ void RestoreState(const State& state)
+ {
+ m_state = state;
+ }
+
+ const Token& NextToken()
+ {
+ if (m_state.m_cur == m_state.m_end)
+ return EofToken();
+
+ return *m_state.m_cur ++;
+ }
+
+ void EatToken()
+ {
+ if (m_state.m_cur != m_state.m_end)
+ ++ m_state.m_cur;
+ }
+
+ void ReturnToken()
+ {
+ if (m_state.m_cur != m_state.m_begin)
+ -- m_state.m_cur;
+ }
+
+ const Token& PeekNextToken() const
+ {
+ if (m_state.m_cur == m_state.m_end)
+ return EofToken();
+
+ return *m_state.m_cur;
+ }
+
+ bool EatIfEqual(char type, Token* tok = nullptr)
+ {
+ return EatIfEqual(static_cast<Token::Type>(type), tok);
+ }
+
+ bool EatIfEqual(Token::Type type, Token* tok = nullptr)
+ {
+ if (m_state.m_cur == m_state.m_end)
+ {
+ if(type == Token::Type::Eof && tok)
+ *tok = EofToken();
+
+ return type == Token::Type::Eof;
+ }
+
+ return EatIfEqualImpl(tok, [type](const Token& t) {return t.type == type;});
+ }
+
+ auto GetAsKeyword(const Token& tok) const
+ {
+ return m_helper->GetKeyword(tok.range);
+ }
+
+ bool EatIfEqual(Keyword kwType, Token* tok = nullptr)
+ {
+ if (m_state.m_cur == m_state.m_end)
+ return false;
+
+ return EatIfEqualImpl(tok, [this, kwType](const Token& t) {return GetAsKeyword(t) == kwType;});
+ }
+
+private:
+ template<typename Fn>
+ bool EatIfEqualImpl(Token* tok, Fn&& predicate)
+ {
+ if (predicate(*m_state.m_cur))
+ {
+ if (tok)
+ *tok = *m_state.m_cur;
+ ++ m_state.m_cur;
+ return true;
+ }
+
+ return false;
+ }
+
+private:
+ State m_state;
+ LexerHelper* m_helper;
+
+ static const Token& EofToken()
+ {
+ static Token eof;
+ eof.type = Token::Eof;
+ return eof;
+ }
+};
+
+} // namespace jinja2
+
+namespace std
+{
+template<>
+struct hash<jinja2::Keyword>
+{
+ size_t operator()(jinja2::Keyword kw) const
+ {
+ return std::hash<int>{}(static_cast<int>(kw));
+ }
+};
+} // namespace std
+
+#endif // JINJA2CPP_SRC_LEXER_H
diff --git a/contrib/libs/jinja2cpp/src/lexertk.h b/contrib/libs/jinja2cpp/src/lexertk.h
new file mode 100644
index 0000000000..c318cc7f3d
--- /dev/null
+++ b/contrib/libs/jinja2cpp/src/lexertk.h
@@ -0,0 +1,1842 @@
+/*
+ *****************************************************************
+ * Simple C++ Lexer Toolkit Library *
+ * *
+ * Author: Arash Partow (2001) *
+ * Modified: Flex Ferrum (2018)
+ * URL: http://www.partow.net/programming/lexertk/index.html *
+ * *
+ * Copyright notice: *
+ * Free use of the Simple C++ Lexer Toolkit Library is permitted *
+ * under the guidelines and in accordance with the MIT License. *
+ * http://www.opensource.org/licenses/MIT *
+ * *
+ * *
+ * The lexer will tokenize input against the following BNF: *
+ * *
+ * expression ::= term { +|- term } *
+ * term ::= (symbol | factor) {operator symbol | factor} *
+ * factor ::= symbol | ( '(' {-} expression ')' ) *
+ * symbol ::= number | gensymb | string *
+ * gensymb ::= alphabet {alphabet | digit} *
+ * string ::= '"' {alphabet | digit | operator } '"' *
+ * operator ::= * | / | % | ^ | < | > | <= | >= | << | >> != *
+ * alphabet ::= a | b | .. | z | A | B | .. | Z *
+ * digit ::= 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 *
+ * sign ::= + | - *
+ * edef ::= e | E *
+ * decimal ::= {digit} (digit [.] | [.] digit) {digit} *
+ * exponent ::= edef [sign] digit {digit} *
+ * real ::= [sign] decimal [exponent] *
+ * integer ::= [sign] {digit} *
+ * number ::= real | integer *
+ * *
+ * *
+ * Note: This lexer has been taken from the ExprTk Library. *
+ * *
+ *****************************************************************
+*/
+
+
+#ifndef JINJA2CPP_SRC_LEXERTK_H
+#define JINJA2CPP_SRC_LEXERTK_H
+
+#include <algorithm>
+#include <cctype>
+#include <clocale>
+#include <cstddef>
+#include <cstdio>
+#include <cstdlib>
+#include <deque>
+#include <exception>
+#include <limits>
+#include <locale>
+#include <map>
+#include <set>
+#include <stack>
+#include <stdexcept>
+#include <string>
+#include <vector>
+
+
+namespace lexertk
+{
+ template<typename CharT>
+ struct CharRange
+ {
+ CharT* start;
+ CharT* end;
+
+ auto length() const {return end - start;}
+ auto offset(CharT* from) const {return start - from;}
+ auto operator[] (size_t idx) const {return start[idx];}
+ };
+ namespace details
+ {
+#if 0
+ inline bool is_whitespace(const char c)
+ {
+ return (' ' == c) || ('\n' == c) ||
+ ('\r' == c) || ('\t' == c) ||
+ ('\b' == c) || ('\v' == c) ||
+ ('\f' == c) ;
+ }
+
+
+ inline bool is_letter(const char c)
+ {
+ return (('a' <= c) && (c <= 'z')) || (('A' <= c) && (c <= 'Z'));
+ }
+
+ inline bool is_digit(const char c)
+ {
+ return ('0' <= c) && (c <= '9');
+ }
+
+ inline bool is_letter_or_digit(const char c)
+ {
+ return is_letter(c) || is_digit(c);
+ }
+#endif
+ template<typename CharT>
+ struct lexer_traits
+ {
+ static auto& get_locale()
+ {
+ static auto locale = std::locale();
+ return locale;
+ }
+
+ static bool is_whitespace(const CharT c)
+ {
+ return std::isspace(c, get_locale());
+ }
+ static bool is_letter(const CharT c)
+ {
+ return std::isalpha(c, get_locale());
+ }
+ static bool is_digit(const CharT c)
+ {
+ return std::isdigit(c, get_locale());
+ }
+ static bool is_letter_or_digit(CharT c)
+ {
+ return std::isalnum(c, get_locale());
+ }
+ static bool is_operator_char(const CharT c);
+ static bool is_left_bracket(const CharT c);
+ static bool is_right_bracket(const CharT c);
+ static bool is_sign(const CharT c);
+ static bool is_invalid(const CharT c);
+ static bool is_bracket(const CharT c)
+ {
+ return is_left_bracket(c) || is_right_bracket(c);
+ }
+ static CharT tolower(const CharT c)
+ {
+ return std::tolower(c, get_locale());
+ }
+ static CharT toupper(const CharT c)
+ {
+ return std::toupper(c, get_locale());
+ }
+ static inline bool imatch(const CharT c1, const CharT c2)
+ {
+ return tolower(c1) == tolower(c2);
+ }
+
+ static inline bool imatch(const CharRange<CharT>& s1, const CharRange<CharT>& s2)
+ {
+ if (s1.length() == s2.length())
+ {
+ for (std::size_t i = 0; i < s1.length(); ++i)
+ {
+ if (tolower(s1[i]) != tolower(s2[i]))
+ {
+ return false;
+ }
+ }
+
+ return true;
+ }
+
+ return false;
+ }
+ };
+
+ template<>
+ inline bool lexer_traits<char>::is_operator_char(const char c)
+ {
+ return ('+' == c) || ('-' == c) ||
+ ('*' == c) || ('/' == c) ||
+ ('^' == c) || ('<' == c) ||
+ ('>' == c) || ('=' == c) ||
+ (',' == c) || ('!' == c) ||
+ ('(' == c) || (')' == c) ||
+ ('[' == c) || (']' == c) ||
+ ('{' == c) || ('}' == c) ||
+ ('%' == c) || (':' == c) ||
+ ('?' == c) || ('&' == c) ||
+ ('|' == c) || (';' == c) ||
+ ('~' == c);
+ }
+
+ template<>
+ inline bool lexer_traits<wchar_t>::is_operator_char(const wchar_t c)
+ {
+ return (L'+' == c) || (L'-' == c) ||
+ (L'*' == c) || (L'/' == c) ||
+ (L'^' == c) || (L'<' == c) ||
+ (L'>' == c) || (L'=' == c) ||
+ (L',' == c) || (L'!' == c) ||
+ (L'(' == c) || (L')' == c) ||
+ (L'[' == c) || (L']' == c) ||
+ (L'{' == c) || (L'}' == c) ||
+ (L'%' == c) || (L':' == c) ||
+ (L'?' == c) || (L'&' == c) ||
+ (L'|' == c) || (L';' == c) ||
+ (L'~' == c);
+ }
+
+ template<>
+ inline bool lexer_traits<char>::is_left_bracket(const char c)
+ {
+ return ('(' == c) || ('[' == c) || ('{' == c);
+ }
+ template<>
+ inline bool lexer_traits<wchar_t>::is_left_bracket(const wchar_t c)
+ {
+ return (L'(' == c) || (L'[' == c) || (L'{' == c);
+ }
+
+ template<>
+ inline bool lexer_traits<char>::is_right_bracket(const char c)
+ {
+ return (')' == c) || (']' == c) || ('}' == c);
+ }
+
+ template<>
+ inline bool lexer_traits<wchar_t>::is_right_bracket(const wchar_t c)
+ {
+ return (L')' == c) || (L']' == c) || (L'}' == c);
+ }
+
+ template<>
+ inline bool lexer_traits<char>::is_sign(const char c)
+ {
+ return ('+' == c) || ('-' == c);
+ }
+
+ template<>
+ inline bool lexer_traits<wchar_t>::is_sign(const wchar_t c)
+ {
+ return (L'+' == c) || (L'-' == c);
+ }
+
+ template<>
+ inline bool lexer_traits<char>::is_invalid(const char c)
+ {
+ return !is_whitespace(c) &&
+ !is_operator_char(c) &&
+ !is_letter(c) &&
+ !is_digit(c) &&
+ ('.' != c) &&
+ ('_' != c) &&
+ ('$' != c) &&
+ ('~' != c) &&
+ ('\'' != c);
+ }
+
+ template<>
+ inline bool lexer_traits<wchar_t>::is_invalid(const wchar_t c)
+ {
+ return !is_whitespace(c) &&
+ !is_operator_char(c) &&
+ !is_letter(c) &&
+ !is_digit(c) &&
+ (L'.' != c) &&
+ (L'_' != c) &&
+ (L'$' != c) &&
+ (L'~' != c) &&
+ (L'\'' != c);
+ }
+
+ template<typename CharT>
+ struct ilesscompare
+ {
+ inline bool operator()(const CharRange<CharT>& s1, const CharRange<CharT>& s2) const
+ {
+ using traits = lexer_traits<CharT>;
+ const std::size_t length = std::min(s1.length(),s2.length());
+
+ for (std::size_t i = 0; i < length; ++i)
+ {
+ if (traits::tolower(s1[i]) > traits::tolower(s2[i]))
+ return false;
+ else if (traits::tolower(s1[i]) < traits::tolower(s2[i]))
+ return true;
+ }
+
+ return s1.length() < s2.length();
+ }
+ };
+
+ } // namespace details
+
+ struct token
+ {
+
+ enum token_type
+ {
+ e_none = 0, e_error = 1, e_err_symbol = 2,
+ e_err_number = 3, e_err_string = 4, e_err_sfunc = 5,
+ e_eof = 6, e_number = 7, e_symbol = 8,
+ e_string = 9, e_eq = 10, e_shr = 11,
+ e_shl = 12, e_lte = 13, e_ne = 14,
+ e_gte = 15, e_lt = '<', e_gt = '>',
+ e_rbracket = ')', e_lbracket = '(', e_tilda = '~',
+ e_rsqrbracket = ']', e_lsqrbracket = '[', e_rcrlbracket = '}',
+ e_lcrlbracket = '{', e_comma = ',', e_add = '+',
+ e_sub = '-', e_div = '/', e_mul = '*',
+ e_mod = '%', e_pow = '^', e_colon = ':',
+ e_dot = '.', e_divdiv = 16 , e_mulmul = 17 ,
+ e_assign = '=', e_pipe = '|',
+ };
+
+ token()
+ : type(e_none),
+ position(std::numeric_limits<std::size_t>::max()),
+ length(0)
+ {}
+
+ void clear()
+ {
+ type = e_none;
+ position = std::numeric_limits<std::size_t>::max();
+ }
+
+ template <typename Iterator>
+ inline token& set_operator(const token_type tt, const Iterator begin, const Iterator end, const Iterator base_begin)
+ {
+ type = tt;
+ position = std::distance(base_begin,begin);
+ length = end - begin;
+ return *this;
+ }
+
+ template <typename Iterator>
+ inline token& set_symbol(const Iterator begin, const Iterator end, const Iterator base_begin)
+ {
+ type = e_symbol;
+ position = std::distance(base_begin,begin);
+ length = end - begin;
+ return *this;
+ }
+
+ template <typename Iterator>
+ inline token& set_numeric(const Iterator begin, const Iterator end, const Iterator base_begin)
+ {
+ type = e_number;
+ position = std::distance(base_begin,begin);
+ length = end - begin;
+ return *this;
+ }
+
+ template <typename Iterator>
+ inline token& set_string(const Iterator begin, const Iterator end, const Iterator base_begin)
+ {
+ type = e_string;
+ position = std::distance(base_begin,begin);
+ length = end - begin;
+ return *this;
+ }
+
+ template <typename Iterator>
+ inline token& set_error(const token_type et, const Iterator begin, const Iterator end, const Iterator base_begin)
+ {
+ if (
+ (e_error == et) ||
+ (e_err_symbol == et) ||
+ (e_err_number == et) ||
+ (e_err_string == et)
+ )
+ {
+ type = e_error;
+ }
+ else
+ type = e_error;
+
+ position = std::distance(base_begin,begin);
+ length = end - begin;
+
+ return *this;
+ }
+
+ static inline const char* to_str(token_type t)
+ {
+ switch (t)
+ {
+ case e_none : return "NONE";
+ case e_error : return "ERROR";
+ case e_err_symbol : return "ERROR_SYMBOL";
+ case e_err_number : return "ERROR_NUMBER";
+ case e_err_string : return "ERROR_STRING";
+ case e_eof : return "EOF";
+ case e_number : return "NUMBER";
+ case e_symbol : return "SYMBOL";
+ case e_string : return "STRING";
+ case e_assign : return "=";
+ case e_shr : return ">>";
+ case e_shl : return "<<";
+ case e_lte : return "<=";
+ case e_ne : return "!=";
+ case e_gte : return ">=";
+ case e_lt : return "<";
+ case e_gt : return ">";
+ case e_eq : return "==";
+ case e_rbracket : return ")";
+ case e_lbracket : return "(";
+ case e_rsqrbracket : return "]";
+ case e_lsqrbracket : return "[";
+ case e_rcrlbracket : return "}";
+ case e_lcrlbracket : return "{";
+ case e_comma : return ",";
+ case e_dot : return ".";
+ case e_add : return "+";
+ case e_sub : return "-";
+ case e_div : return "/";
+ case e_mul : return "*";
+ case e_mod : return "%";
+ case e_pow : return "^";
+ case e_colon : return ":";
+ case e_divdiv : return "//";
+ case e_mulmul : return "**";
+ default : return "UNKNOWN";
+ }
+ }
+
+ inline bool is_error() const
+ {
+ return (
+ (e_error == type) ||
+ (e_err_symbol == type) ||
+ (e_err_number == type) ||
+ (e_err_string == type)
+ );
+ }
+
+ token_type type;
+ size_t position;
+ size_t length;
+ };
+
+ template<typename CharT>
+ class generator
+ {
+ public:
+
+ typedef token token_t;
+ typedef std::vector<token_t> token_list_t;
+ typedef std::vector<token_t>::iterator token_list_itr_t;
+ typedef details::lexer_traits<CharT> traits;
+
+ generator()
+ : base_itr_(0),
+ s_itr_(0),
+ s_end_(0)
+ {
+ clear();
+ }
+
+ inline void clear()
+ {
+ base_itr_ = 0;
+ s_itr_ = 0;
+ s_end_ = 0;
+ token_list_.clear();
+ token_itr_ = token_list_.end();
+ store_token_itr_ = token_itr_;
+ }
+
+ inline bool process(const std::basic_string<CharT>& str)
+ {
+ return process(str.data(), str.data() + str.size());
+ }
+
+ inline bool process(const CharT* begin, const CharT* end)
+ {
+ base_itr_ = begin;
+ s_itr_ = begin;
+ s_end_ = end;
+
+ eof_token_.set_operator(token_t::e_eof,s_end_,s_end_,base_itr_);
+ token_list_.clear();
+
+ while (!is_end(s_itr_))
+ {
+ scan_token();
+
+ if (token_list_.empty())
+ return true;
+ else if (token_list_.back().is_error())
+ {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ inline bool empty() const
+ {
+ return token_list_.empty();
+ }
+
+ inline size_t size() const
+ {
+ return token_list_.size();
+ }
+
+ inline void begin()
+ {
+ token_itr_ = token_list_.begin();
+ store_token_itr_ = token_itr_;
+ }
+
+ inline void store()
+ {
+ store_token_itr_ = token_itr_;
+ }
+
+ inline void restore()
+ {
+ token_itr_ = store_token_itr_;
+ }
+
+ inline token_t& next_token()
+ {
+ if (token_list_.end() != token_itr_)
+ {
+ return *token_itr_++;
+ }
+ else
+ return eof_token_;
+ }
+
+ inline token_t& peek_next_token()
+ {
+ if (token_list_.end() != token_itr_)
+ {
+ return *token_itr_;
+ }
+ else
+ return eof_token_;
+ }
+
+ inline token_t& operator[](const std::size_t& index)
+ {
+ if (index < token_list_.size())
+ return token_list_[index];
+ else
+ return eof_token_;
+ }
+
+ inline token_t operator[](const std::size_t& index) const
+ {
+ if (index < token_list_.size())
+ return token_list_[index];
+ else
+ return eof_token_;
+ }
+
+ inline bool finished() const
+ {
+ return (token_list_.end() == token_itr_);
+ }
+
+ inline std::basic_string<CharT> remaining() const
+ {
+ using string = std::basic_string<CharT>;
+ if (finished())
+ return string();
+ else if (token_list_.begin() != token_itr_)
+ return string(base_itr_ + (token_itr_ - 1)->position,s_end_);
+ else
+ return string(base_itr_ + token_itr_->position,s_end_);
+ }
+
+ private:
+
+ inline bool is_end(const CharT* itr)
+ {
+ return (s_end_ == itr);
+ }
+
+ inline void skip_whitespace()
+ {
+ while (!is_end(s_itr_) && traits::is_whitespace(*s_itr_))
+ {
+ ++s_itr_;
+ }
+ }
+ inline void scan_token()
+ {
+ skip_whitespace();
+
+ if (is_end(s_itr_))
+ {
+ return;
+ }
+ else if (traits::is_operator_char(*s_itr_))
+ {
+ scan_operator();
+ return;
+ }
+ else if (traits::is_letter(*s_itr_) || ('_' == (*s_itr_)))
+ {
+ scan_symbol();
+ return;
+ }
+ else if (traits::is_digit((*s_itr_)) || ('.' == (*s_itr_)))
+ {
+ scan_number();
+ return;
+ }
+ else if ('\'' == (*s_itr_) || '\"' == (*s_itr_))
+ {
+ scan_string();
+ return;
+ }
+ else
+ {
+ token_t t;
+ t.set_error(token::e_error,s_itr_,s_itr_ + 2,base_itr_);
+ token_list_.push_back(t);
+ ++s_itr_;
+ }
+ }
+
+ inline void scan_operator()
+ {
+ token_t t;
+
+ if (!is_end(s_itr_ + 1))
+ {
+ token_t::token_type ttype = token_t::e_none;
+
+ CharT c0 = s_itr_[0];
+ CharT c1 = s_itr_[1];
+
+ if ((c0 == '<') && (c1 == '=')) ttype = token_t::e_lte;
+ else if ((c0 == '>') && (c1 == '=')) ttype = token_t::e_gte;
+ else if ((c0 == '<') && (c1 == '>')) ttype = token_t::e_ne;
+ else if ((c0 == '!') && (c1 == '=')) ttype = token_t::e_ne;
+ else if ((c0 == '=') && (c1 == '=')) ttype = token_t::e_eq;
+ else if ((c0 == ':') && (c1 == '=')) ttype = token_t::e_assign;
+ else if ((c0 == '<') && (c1 == '<')) ttype = token_t::e_shl;
+ else if ((c0 == '>') && (c1 == '>')) ttype = token_t::e_shr;
+ else if ((c0 == '*') && (c1 == '*')) ttype = token_t::e_mulmul;
+ else if ((c0 == '/') && (c1 == '/')) ttype = token_t::e_divdiv;
+
+ if (token_t::e_none != ttype)
+ {
+ t.set_operator(ttype,s_itr_,s_itr_ + 2,base_itr_);
+ token_list_.push_back(t);
+ s_itr_ += 2;
+ return;
+ }
+ }
+
+ if ('<' == *s_itr_)
+ t.set_operator(token_t::e_lt ,s_itr_,s_itr_ + 1,base_itr_);
+ else if ('>' == *s_itr_)
+ t.set_operator(token_t::e_gt ,s_itr_,s_itr_ + 1,base_itr_);
+ else if (';' == *s_itr_)
+ t.set_operator(token_t::e_eof,s_itr_,s_itr_ + 1,base_itr_);
+ else if ('&' == *s_itr_)
+ t.set_symbol(s_itr_,s_itr_ + 1,base_itr_);
+ else if ('|' == *s_itr_)
+ t.set_operator(token::e_pipe,s_itr_,s_itr_ + 1,base_itr_);
+ else
+ t.set_operator(token_t::token_type(*s_itr_),s_itr_,s_itr_ + 1,base_itr_);
+
+ token_list_.push_back(t);
+
+ ++s_itr_;
+ }
+
+ inline void scan_symbol()
+ {
+ const CharT* begin = s_itr_;
+ while (
+ (!is_end(s_itr_)) &&
+ (traits::is_letter_or_digit(*s_itr_) || ((*s_itr_) == '_'))
+ )
+ {
+ ++s_itr_;
+ }
+ token_t t;
+ t.set_symbol(begin,s_itr_,base_itr_);
+ token_list_.push_back(t);
+ }
+
+ inline void scan_number()
+ {
+ /*
+ Attempt to match a valid numeric value in one of the following formats:
+ 01. 123456
+ 02. 123.456
+ 03. 123.456e3
+ 04. 123.456E3
+ 05. 123.456e+3
+ 06. 123.456E+3
+ 07. 123.456e-3
+ 08. 123.456E-3
+ 09. .1234
+ 10. .1234e3
+ 11. .1234E+3
+ 12. .1234e+3
+ 13. .1234E-3
+ 14. .1234e-3
+ */
+ const CharT* begin = s_itr_;
+ bool dot_found = false;
+ bool e_found = false;
+ bool post_e_sign_found = false;
+ bool post_e_digit_found = false;
+ token_t t;
+
+ if ('.' == *begin && !is_end(begin + 1) && !traits::is_digit(begin[1]))
+ {
+ scan_operator();
+ return;
+ }
+
+ while (!is_end(s_itr_))
+ {
+ if ('.' == (*s_itr_))
+ {
+ if (dot_found)
+ {
+ t.set_error(token::e_err_number,begin,s_itr_,base_itr_);
+ token_list_.push_back(t);
+
+ return;
+ }
+
+ dot_found = true;
+ ++s_itr_;
+
+ continue;
+ }
+ else if (traits::imatch('e',(*s_itr_)))
+ {
+ const CharT& c = *(s_itr_ + 1);
+
+ if (is_end(s_itr_ + 1))
+ {
+ t.set_error(token::e_err_number,begin,s_itr_,base_itr_);
+ token_list_.push_back(t);
+
+ return;
+ }
+ else if (
+ ('+' != c) &&
+ ('-' != c) &&
+ !traits::is_digit(c)
+ )
+ {
+ t.set_error(token::e_err_number,begin,s_itr_,base_itr_);
+ token_list_.push_back(t);
+
+ return;
+ }
+
+ e_found = true;
+ ++s_itr_;
+
+ continue;
+ }
+ else if (e_found && traits::is_sign(*s_itr_) && !post_e_digit_found)
+ {
+ if (post_e_sign_found)
+ {
+ t.set_error(token::e_err_number,begin,s_itr_,base_itr_);
+ token_list_.push_back(t);
+
+ return;
+ }
+
+ post_e_sign_found = true;
+ ++s_itr_;
+
+ continue;
+ }
+ else if (e_found && traits::is_digit(*s_itr_))
+ {
+ post_e_digit_found = true;
+ ++s_itr_;
+
+ continue;
+ }
+ else if (('.' != (*s_itr_)) && !traits::is_digit(*s_itr_))
+ break;
+ else
+ ++s_itr_;
+ }
+
+ t.set_numeric(begin,s_itr_,base_itr_);
+
+ token_list_.push_back(t);
+
+ return;
+ }
+
+ inline void scan_string()
+ {
+ CharT endChar = *s_itr_;
+ const CharT* begin = s_itr_ + 1;
+
+ token_t t;
+
+ if (std::distance(s_itr_,s_end_) < 2)
+ {
+ t.set_error(token::e_err_string,s_itr_,s_end_,base_itr_);
+ token_list_.push_back(t);
+
+ return;
+ }
+
+ ++s_itr_;
+
+ bool escaped = false;
+
+ while (!is_end(s_itr_))
+ {
+ if (!escaped && ('\\' == *s_itr_))
+ {
+ escaped = true;
+ ++s_itr_;
+
+ continue;
+ }
+ else if (!escaped)
+ {
+ if (endChar == *s_itr_)
+ break;
+ }
+ else
+ escaped = false;
+
+ ++s_itr_;
+ }
+
+ if (is_end(s_itr_))
+ {
+ t.set_error(token::e_err_string,begin,s_itr_,base_itr_);
+ token_list_.push_back(t);
+
+ return;
+ }
+
+ t.set_string(begin,s_itr_,base_itr_);
+ token_list_.push_back(t);
+ ++s_itr_;
+
+ return;
+ }
+
+ private:
+
+ token_list_t token_list_;
+ token_list_itr_t token_itr_;
+ token_list_itr_t store_token_itr_;
+ token_t eof_token_;
+ const CharT* base_itr_;
+ const CharT* s_itr_;
+ const CharT* s_end_;
+
+ friend class token_scanner;
+ friend class token_modifier;
+ friend class token_inserter;
+ friend class token_joiner;
+ };
+#if 0
+ class helper_interface
+ {
+ public:
+
+ virtual void init() { }
+ virtual void reset() { }
+ virtual bool result() { return true; }
+ virtual std::size_t process(generator&) { return 0; }
+ virtual ~helper_interface() { }
+ };
+
+ class token_scanner : public helper_interface
+ {
+ public:
+
+ virtual ~token_scanner()
+ {}
+
+ explicit token_scanner(const std::size_t& stride)
+ : stride_(stride)
+ {
+ if (stride > 4)
+ {
+ throw std::invalid_argument("token_scanner() - Invalid stride value");
+ }
+ }
+
+ inline std::size_t process(generator& g)
+ {
+ if (!g.token_list_.empty())
+ {
+ for (std::size_t i = 0; i < (g.token_list_.size() - stride_ + 1); ++i)
+ {
+ token t;
+ switch (stride_)
+ {
+ case 1 :
+ {
+ const token& t0 = g.token_list_[i];
+
+ if (!operator()(t0)) return i;
+ }
+ break;
+
+ case 2 :
+ {
+ const token& t0 = g.token_list_[i ];
+ const token& t1 = g.token_list_[i + 1];
+
+ if (!operator()(t0,t1)) return i;
+ }
+ break;
+
+ case 3 :
+ {
+ const token& t0 = g.token_list_[i ];
+ const token& t1 = g.token_list_[i + 1];
+ const token& t2 = g.token_list_[i + 2];
+
+ if (!operator()(t0,t1,t2)) return i;
+ }
+ break;
+
+ case 4 :
+ {
+ const token& t0 = g.token_list_[i ];
+ const token& t1 = g.token_list_[i + 1];
+ const token& t2 = g.token_list_[i + 2];
+ const token& t3 = g.token_list_[i + 3];
+
+ if (!operator()(t0,t1,t2,t3)) return i;
+ }
+ break;
+ }
+ }
+ }
+
+ return (g.token_list_.size() - stride_ + 1);
+ }
+
+ virtual bool operator()(const token&)
+ {
+ return false;
+ }
+
+ virtual bool operator()(const token&, const token&)
+ {
+ return false;
+ }
+
+ virtual bool operator()(const token&, const token&, const token&)
+ {
+ return false;
+ }
+
+ virtual bool operator()(const token&, const token&, const token&, const token&)
+ {
+ return false;
+ }
+
+ private:
+
+ std::size_t stride_;
+ };
+
+ class token_modifier : public helper_interface
+ {
+ public:
+
+ inline std::size_t process(generator& g)
+ {
+ std::size_t changes = 0;
+
+ for (std::size_t i = 0; i < g.token_list_.size(); ++i)
+ {
+ if (modify(g.token_list_[i])) changes++;
+ }
+
+ return changes;
+ }
+
+ virtual bool modify(token& t) = 0;
+ };
+
+ class token_inserter : public helper_interface
+ {
+ public:
+
+ explicit token_inserter(const std::size_t& stride)
+ : stride_(stride)
+ {
+ if (stride > 5)
+ {
+ throw std::invalid_argument("token_inserter() - Invalid stride value");
+ }
+ }
+
+ inline std::size_t process(generator& g)
+ {
+ if (g.token_list_.empty())
+ return 0;
+
+ std::size_t changes = 0;
+
+ for (std::size_t i = 0; i < (g.token_list_.size() - stride_ + 1); ++i)
+ {
+ token t;
+ int insert_index = -1;
+
+ switch (stride_)
+ {
+ case 1 : insert_index = insert(g.token_list_[i],t);
+ break;
+
+ case 2 : insert_index = insert(g.token_list_[i],g.token_list_[i + 1],t);
+ break;
+
+ case 3 : insert_index = insert(g.token_list_[i],g.token_list_[i + 1],g.token_list_[i + 2],t);
+ break;
+
+ case 4 : insert_index = insert(g.token_list_[i],g.token_list_[i + 1],g.token_list_[i + 2],g.token_list_[i + 3],t);
+ break;
+
+ case 5 : insert_index = insert(g.token_list_[i],g.token_list_[i + 1],g.token_list_[i + 2],g.token_list_[i + 3],g.token_list_[i + 4],t);
+ break;
+ }
+
+ if ((insert_index >= 0) && (insert_index <= (static_cast<int>(stride_) + 1)))
+ {
+ g.token_list_.insert(g.token_list_.begin() + (i + insert_index),t);
+ changes++;
+ }
+ }
+
+ return changes;
+ }
+
+ virtual inline int insert(const token&, token& )
+ {
+ return -1;
+ }
+
+ virtual inline int insert(const token&, const token&, token&)
+ {
+ return -1;
+ }
+
+ virtual inline int insert(const token&, const token&, const token&, token&)
+ {
+ return -1;
+ }
+
+ virtual inline int insert(const token&, const token&, const token&, const token&, token&)
+ {
+ return -1;
+ }
+
+ virtual inline int insert(const token&, const token&, const token&, const token&, const token&, token&)
+ {
+ return -1;
+ }
+
+ private:
+
+ std::size_t stride_;
+ };
+
+ class token_joiner : public helper_interface
+ {
+ public:
+
+ inline std::size_t process(generator& g)
+ {
+ if (g.token_list_.empty())
+ return 0;
+
+ std::size_t changes = 0;
+
+ for (std::size_t i = 0; i < g.token_list_.size() - 1; ++i)
+ {
+ token t;
+
+ if (join(g.token_list_[i],g.token_list_[i + 1],t))
+ {
+ g.token_list_[i] = t;
+ g.token_list_.erase(g.token_list_.begin() + (i + 1));
+
+ ++changes;
+ }
+ }
+
+ return changes;
+ }
+
+ virtual bool join(const token&, const token&, token&) = 0;
+ };
+
+ namespace helper
+ {
+
+ inline void dump(lexertk::generator& generator)
+ {
+ for (std::size_t i = 0; i < generator.size(); ++i)
+ {
+ lexertk::token t = generator[i];
+ printf("Token[%02d] @ %03d %6s --> '%s'\n",
+ static_cast<unsigned int>(i),
+ static_cast<unsigned int>(t.position),
+ t.to_str(t.type).c_str(),
+ t.value.c_str());
+ }
+ }
+
+ class commutative_inserter : public token_inserter
+ {
+ public:
+
+ commutative_inserter()
+ : lexertk::token_inserter(2)
+ {}
+
+ inline void ignore_symbol(const std::string& symbol)
+ {
+ ignore_set_.insert(symbol);
+ }
+
+ inline int insert(const lexertk::token& t0, const lexertk::token& t1, lexertk::token& new_token)
+ {
+ new_token.type = lexertk::token::e_mul;
+ new_token.value = "*";
+ new_token.position = t1.position;
+ bool match = false;
+
+ if (t0.type == lexertk::token::e_symbol)
+ {
+ if (ignore_set_.end() != ignore_set_.find(t0.value))
+ {
+ return -1;
+ }
+ else if (!t0.value.empty() && ('$' == t0.value[0]))
+ {
+ return -1;
+ }
+ }
+
+ if (t1.type == lexertk::token::e_symbol)
+ {
+ if (ignore_set_.end() != ignore_set_.find(t1.value))
+ {
+ return -1;
+ }
+ }
+
+ if ((t0.type == lexertk::token::e_number ) && (t1.type == lexertk::token::e_symbol )) match = true;
+ else if ((t0.type == lexertk::token::e_number ) && (t1.type == lexertk::token::e_lbracket )) match = true;
+ else if ((t0.type == lexertk::token::e_number ) && (t1.type == lexertk::token::e_lcrlbracket)) match = true;
+ else if ((t0.type == lexertk::token::e_number ) && (t1.type == lexertk::token::e_lsqrbracket)) match = true;
+ else if ((t0.type == lexertk::token::e_symbol ) && (t1.type == lexertk::token::e_number )) match = true;
+ else if ((t0.type == lexertk::token::e_rbracket ) && (t1.type == lexertk::token::e_number )) match = true;
+ else if ((t0.type == lexertk::token::e_rcrlbracket) && (t1.type == lexertk::token::e_number )) match = true;
+ else if ((t0.type == lexertk::token::e_rsqrbracket) && (t1.type == lexertk::token::e_number )) match = true;
+ else if ((t0.type == lexertk::token::e_rbracket ) && (t1.type == lexertk::token::e_symbol )) match = true;
+ else if ((t0.type == lexertk::token::e_rcrlbracket) && (t1.type == lexertk::token::e_symbol )) match = true;
+ else if ((t0.type == lexertk::token::e_rsqrbracket) && (t1.type == lexertk::token::e_symbol )) match = true;
+
+ return (match) ? 1 : -1;
+ }
+
+ private:
+
+ std::set<std::string,details::ilesscompare> ignore_set_;
+ };
+
+ class operator_joiner : public token_joiner
+ {
+ public:
+
+ inline bool join(const lexertk::token& t0, const lexertk::token& t1, lexertk::token& t)
+ {
+ //': =' --> ':='
+ if ((t0.type == lexertk::token::e_colon) && (t1.type == lexertk::token::e_eq))
+ {
+ t.type = lexertk::token::e_assign;
+ t.value = ":=";
+ t.position = t0.position;
+
+ return true;
+ }
+ //'> =' --> '>='
+ else if ((t0.type == lexertk::token::e_gt) && (t1.type == lexertk::token::e_eq))
+ {
+ t.type = lexertk::token::e_gte;
+ t.value = ">=";
+ t.position = t0.position;
+
+ return true;
+ }
+ //'< =' --> '<='
+ else if ((t0.type == lexertk::token::e_lt) && (t1.type == lexertk::token::e_eq))
+ {
+ t.type = lexertk::token::e_lte;
+ t.value = "<=";
+ t.position = t0.position;
+
+ return true;
+ }
+ //'= =' --> '=='
+ else if ((t0.type == lexertk::token::e_eq) && (t1.type == lexertk::token::e_eq))
+ {
+ t.type = lexertk::token::e_eq;
+ t.value = "==";
+ t.position = t0.position;
+
+ return true;
+ }
+ //'! =' --> '!='
+ else if ((static_cast<char>(t0.type) == '!') && (t1.type == lexertk::token::e_eq))
+ {
+ t.type = lexertk::token::e_ne;
+ t.value = "!=";
+ t.position = t0.position;
+
+ return true;
+ }
+ //'< >' --> '<>'
+ else if ((t0.type == lexertk::token::e_lt) && (t1.type == lexertk::token::e_gt))
+ {
+ t.type = lexertk::token::e_ne;
+ t.value = "<>";
+ t.position = t0.position;
+
+ return true;
+ }
+ else
+ return false;
+ }
+ };
+
+ class bracket_checker : public token_scanner
+ {
+ public:
+
+ bracket_checker()
+ : token_scanner(1),
+ state_(true)
+ {}
+
+ bool result()
+ {
+ return state_ && stack_.empty();
+ }
+
+ lexertk::token error_token()
+ {
+ return error_token_;
+ }
+
+ void reset()
+ {
+ //why? because msvc doesn't support swap properly.
+ stack_ = std::stack<char>();
+ state_ = true;
+ error_token_.clear();
+ }
+
+ bool operator()(const lexertk::token& t)
+ {
+ if (
+ !t.value.empty() &&
+ (lexertk::token::e_string != t.type) &&
+ (lexertk::token::e_symbol != t.type) &&
+ details::is_bracket(t.value[0])
+ )
+ {
+ char c = t.value[0];
+
+ if (t.type == lexertk::token::e_lbracket) stack_.push(')');
+ else if (t.type == lexertk::token::e_lcrlbracket) stack_.push('}');
+ else if (t.type == lexertk::token::e_lsqrbracket) stack_.push(']');
+ else if (details::is_right_bracket(c))
+ {
+ if (stack_.empty())
+ {
+ state_ = false;
+ error_token_ = t;
+
+ return false;
+ }
+ else if (c != stack_.top())
+ {
+ state_ = false;
+ error_token_ = t;
+
+ return false;
+ }
+ else
+ stack_.pop();
+ }
+ }
+
+ return true;
+ }
+
+ private:
+
+ bool state_;
+ std::stack<char> stack_;
+ lexertk::token error_token_;
+ };
+
+ class symbol_replacer : public token_modifier
+ {
+ private:
+
+ typedef std::map<std::string,std::pair<std::string,token::token_type>,details::ilesscompare> replace_map_t;
+
+ public:
+
+ bool remove(const std::string& target_symbol)
+ {
+ replace_map_t::iterator itr = replace_map_.find(target_symbol);
+
+ if (replace_map_.end() == itr)
+ return false;
+
+ replace_map_.erase(itr);
+
+ return true;
+ }
+
+ bool add_replace(const std::string& target_symbol,
+ const std::string& replace_symbol,
+ const lexertk::token::token_type token_type = lexertk::token::e_symbol)
+ {
+ replace_map_t::iterator itr = replace_map_.find(target_symbol);
+
+ if (replace_map_.end() != itr)
+ {
+ return false;
+ }
+
+ replace_map_[target_symbol] = std::make_pair(replace_symbol,token_type);
+
+ return true;
+ }
+
+ void clear()
+ {
+ replace_map_.clear();
+ }
+
+ private:
+
+ bool modify(lexertk::token& t)
+ {
+ if (lexertk::token::e_symbol == t.type)
+ {
+ if (replace_map_.empty())
+ return false;
+
+ replace_map_t::iterator itr = replace_map_.find(t.value);
+
+ if (replace_map_.end() != itr)
+ {
+ t.value = itr->second.first;
+ t.type = itr->second.second;
+
+ return true;
+ }
+ }
+
+ return false;
+ }
+
+ replace_map_t replace_map_;
+ };
+
+ class sequence_validator : public token_scanner
+ {
+ private:
+
+ typedef std::pair<lexertk::token::token_type,lexertk::token::token_type> token_pair_t;
+ typedef std::set<token_pair_t> set_t;
+
+ public:
+
+ sequence_validator()
+ : lexertk::token_scanner(2)
+ {
+ add_invalid(lexertk::token::e_number,lexertk::token::e_number);
+ add_invalid(lexertk::token::e_string,lexertk::token::e_string);
+ add_invalid(lexertk::token::e_number,lexertk::token::e_string);
+ add_invalid(lexertk::token::e_string,lexertk::token::e_number);
+ add_invalid(lexertk::token::e_string,lexertk::token::e_colon);
+ add_invalid(lexertk::token::e_colon,lexertk::token::e_string);
+ add_invalid_set1(lexertk::token::e_assign);
+ add_invalid_set1(lexertk::token::e_shr );
+ add_invalid_set1(lexertk::token::e_shl );
+ add_invalid_set1(lexertk::token::e_lte );
+ add_invalid_set1(lexertk::token::e_ne );
+ add_invalid_set1(lexertk::token::e_gte );
+ add_invalid_set1(lexertk::token::e_lt );
+ add_invalid_set1(lexertk::token::e_gt );
+ add_invalid_set1(lexertk::token::e_eq );
+ add_invalid_set1(lexertk::token::e_comma );
+ add_invalid_set1(lexertk::token::e_add );
+ add_invalid_set1(lexertk::token::e_sub );
+ add_invalid_set1(lexertk::token::e_div );
+ add_invalid_set1(lexertk::token::e_mul );
+ add_invalid_set1(lexertk::token::e_mod );
+ add_invalid_set1(lexertk::token::e_pow );
+ add_invalid_set1(lexertk::token::e_colon );
+ }
+
+ bool result()
+ {
+ return error_list_.empty();
+ }
+
+ bool operator()(const lexertk::token& t0, const lexertk::token& t1)
+ {
+ set_t::value_type p = std::make_pair(t0.type,t1.type);
+
+ if (invalid_bracket_check(t0.type,t1.type))
+ {
+ error_list_.push_back(std::make_pair(t0,t1));
+ }
+ else if (invalid_comb_.find(p) != invalid_comb_.end())
+ error_list_.push_back(std::make_pair(t0,t1));
+
+ return true;
+ }
+
+ std::size_t error_count()
+ {
+ return error_list_.size();
+ }
+
+ std::pair<lexertk::token,lexertk::token> error(const std::size_t index)
+ {
+ if (index < error_list_.size())
+ {
+ return error_list_[index];
+ }
+ else
+ {
+ static const lexertk::token error_token;
+ return std::make_pair(error_token,error_token);
+ }
+ }
+
+ void clear_errors()
+ {
+ error_list_.clear();
+ }
+
+ private:
+
+ void add_invalid(lexertk::token::token_type base, lexertk::token::token_type t)
+ {
+ invalid_comb_.insert(std::make_pair(base,t));
+ }
+
+ void add_invalid_set1(lexertk::token::token_type t)
+ {
+ add_invalid(t,lexertk::token::e_assign);
+ add_invalid(t,lexertk::token::e_shr );
+ add_invalid(t,lexertk::token::e_shl );
+ add_invalid(t,lexertk::token::e_lte );
+ add_invalid(t,lexertk::token::e_ne );
+ add_invalid(t,lexertk::token::e_gte );
+ add_invalid(t,lexertk::token::e_lt );
+ add_invalid(t,lexertk::token::e_gt );
+ add_invalid(t,lexertk::token::e_eq );
+ add_invalid(t,lexertk::token::e_comma );
+ add_invalid(t,lexertk::token::e_div );
+ add_invalid(t,lexertk::token::e_mul );
+ add_invalid(t,lexertk::token::e_mod );
+ add_invalid(t,lexertk::token::e_pow );
+ add_invalid(t,lexertk::token::e_colon );
+ }
+
+ bool invalid_bracket_check(lexertk::token::token_type base, lexertk::token::token_type t)
+ {
+ if (details::is_right_bracket(static_cast<char>(base)))
+ {
+ switch (t)
+ {
+ case lexertk::token::e_string : return true;
+ case lexertk::token::e_assign : return true;
+ default : return false;
+ }
+ }
+ else if (details::is_left_bracket(static_cast<char>(base)))
+ {
+ if (details::is_right_bracket(static_cast<char>(t)))
+ return false;
+ else if (details::is_left_bracket(static_cast<char>(t)))
+ return false;
+ else
+ {
+ switch (t)
+ {
+ case lexertk::token::e_number : return false;
+ case lexertk::token::e_symbol : return false;
+ case lexertk::token::e_string : return false;
+ case lexertk::token::e_add : return false;
+ case lexertk::token::e_sub : return false;
+ case lexertk::token::e_colon : return false;
+ default : return true;
+ }
+ }
+ }
+ else if (details::is_right_bracket(static_cast<char>(t)))
+ {
+ switch (base)
+ {
+ case lexertk::token::e_number : return false;
+ case lexertk::token::e_symbol : return false;
+ case lexertk::token::e_string : return false;
+ case lexertk::token::e_eof : return false;
+ case lexertk::token::e_colon : return false;
+ default : return true;
+ }
+ }
+ else if (details::is_left_bracket(static_cast<char>(t)))
+ {
+ switch (base)
+ {
+ case lexertk::token::e_rbracket : return true;
+ case lexertk::token::e_rsqrbracket : return true;
+ case lexertk::token::e_rcrlbracket : return true;
+ default : return false;
+ }
+ }
+
+ return false;
+ }
+
+ set_t invalid_comb_;
+ std::deque<std::pair<lexertk::token,lexertk::token> > error_list_;
+
+ };
+
+ struct helper_assembly
+ {
+ inline bool register_scanner(lexertk::token_scanner* scanner)
+ {
+ if (token_scanner_list.end() != std::find(token_scanner_list.begin(),
+ token_scanner_list.end(),
+ scanner))
+ {
+ return false;
+ }
+
+ token_scanner_list.push_back(scanner);
+
+ return true;
+ }
+
+ inline bool register_modifier(lexertk::token_modifier* modifier)
+ {
+ if (token_modifier_list.end() != std::find(token_modifier_list.begin(),
+ token_modifier_list.end(),
+ modifier))
+ {
+ return false;
+ }
+
+ token_modifier_list.push_back(modifier);
+
+ return true;
+ }
+
+ inline bool register_joiner(lexertk::token_joiner* joiner)
+ {
+ if (token_joiner_list.end() != std::find(token_joiner_list.begin(),
+ token_joiner_list.end(),
+ joiner))
+ {
+ return false;
+ }
+
+ token_joiner_list.push_back(joiner);
+
+ return true;
+ }
+
+ inline bool register_inserter(lexertk::token_inserter* inserter)
+ {
+ if (token_inserter_list.end() != std::find(token_inserter_list.begin(),
+ token_inserter_list.end(),
+ inserter))
+ {
+ return false;
+ }
+
+ token_inserter_list.push_back(inserter);
+
+ return true;
+ }
+
+ inline bool run_modifiers(lexertk::generator& g)
+ {
+ error_token_modifier = reinterpret_cast<lexertk::token_modifier*>(0);
+
+ for (std::size_t i = 0; i < token_modifier_list.size(); ++i)
+ {
+ lexertk::token_modifier& modifier = (*token_modifier_list[i]);
+
+ modifier.reset();
+ modifier.process(g);
+
+ if (!modifier.result())
+ {
+ error_token_modifier = token_modifier_list[i];
+
+ return false;
+ }
+ }
+
+ return true;
+ }
+
+ inline bool run_joiners(lexertk::generator& g)
+ {
+ error_token_joiner = reinterpret_cast<lexertk::token_joiner*>(0);
+
+ for (std::size_t i = 0; i < token_joiner_list.size(); ++i)
+ {
+ lexertk::token_joiner& joiner = (*token_joiner_list[i]);
+
+ joiner.reset();
+ joiner.process(g);
+
+ if (!joiner.result())
+ {
+ error_token_joiner = token_joiner_list[i];
+
+ return false;
+ }
+ }
+
+ return true;
+ }
+
+ inline bool run_inserters(lexertk::generator& g)
+ {
+ error_token_inserter = reinterpret_cast<lexertk::token_inserter*>(0);
+
+ for (std::size_t i = 0; i < token_inserter_list.size(); ++i)
+ {
+ lexertk::token_inserter& inserter = (*token_inserter_list[i]);
+
+ inserter.reset();
+ inserter.process(g);
+
+ if (!inserter.result())
+ {
+ error_token_inserter = token_inserter_list[i];
+
+ return false;
+ }
+ }
+
+ return true;
+ }
+
+ inline bool run_scanners(lexertk::generator& g)
+ {
+ error_token_scanner = reinterpret_cast<lexertk::token_scanner*>(0);
+
+ for (std::size_t i = 0; i < token_scanner_list.size(); ++i)
+ {
+ lexertk::token_scanner& scanner = (*token_scanner_list[i]);
+
+ scanner.reset();
+ scanner.process(g);
+
+ if (!scanner.result())
+ {
+ error_token_scanner = token_scanner_list[i];
+
+ return false;
+ }
+ }
+
+ return true;
+ }
+
+ std::deque<lexertk::token_scanner*> token_scanner_list;
+ std::deque<lexertk::token_modifier*> token_modifier_list;
+ std::deque<lexertk::token_joiner*> token_joiner_list;
+ std::deque<lexertk::token_inserter*> token_inserter_list;
+
+ lexertk::token_scanner* error_token_scanner;
+ lexertk::token_modifier* error_token_modifier;
+ lexertk::token_joiner* error_token_joiner;
+ lexertk::token_inserter* error_token_inserter;
+ };
+ }
+
+ class parser_helper
+ {
+ public:
+
+ typedef token token_t;
+ typedef generator generator_t;
+
+ inline bool init(const std::string& str)
+ {
+ if (!lexer_.process(str))
+ {
+ return false;
+ }
+
+ lexer_.begin();
+
+ next_token();
+
+ return true;
+ }
+
+ inline generator_t& lexer()
+ {
+ return lexer_;
+ }
+
+ inline const generator_t& lexer() const
+ {
+ return lexer_;
+ }
+
+ inline void next_token()
+ {
+ current_token_ = lexer_.next_token();
+ }
+
+ inline const token_t& current_token() const
+ {
+ return current_token_;
+ }
+
+ enum token_advance_mode
+ {
+ e_hold = 0,
+ e_advance = 1
+ };
+
+ inline void advance_token(const token_advance_mode mode)
+ {
+ if (e_advance == mode)
+ {
+ next_token();
+ }
+ }
+
+ inline bool token_is(const token_t::token_type& ttype, const token_advance_mode mode = e_advance)
+ {
+ if (current_token().type != ttype)
+ {
+ return false;
+ }
+
+ advance_token(mode);
+
+ return true;
+ }
+
+ inline bool token_is(const token_t::token_type& ttype,
+ const std::string& value,
+ const token_advance_mode mode = e_advance)
+ {
+ if (
+ (current_token().type != ttype) ||
+ !details::imatch(value,current_token().value)
+ )
+ {
+ return false;
+ }
+
+ advance_token(mode);
+
+ return true;
+ }
+
+ inline bool token_is_then_assign(const token_t::token_type& ttype,
+ std::string& token,
+ const token_advance_mode mode = e_advance)
+ {
+ if (current_token_.type != ttype)
+ {
+ return false;
+ }
+
+ token = current_token_.value;
+
+ advance_token(mode);
+
+ return true;
+ }
+
+ template <typename Allocator,
+ template <typename,typename> class Container>
+ inline bool token_is_then_assign(const token_t::token_type& ttype,
+ Container<std::string,Allocator>& token_list,
+ const token_advance_mode mode = e_advance)
+ {
+ if (current_token_.type != ttype)
+ {
+ return false;
+ }
+
+ token_list.push_back(current_token_.value);
+
+ advance_token(mode);
+
+ return true;
+ }
+
+ inline bool peek_token_is(const token_t::token_type& ttype)
+ {
+ return (lexer_.peek_next_token().type == ttype);
+ }
+
+ inline bool peek_token_is(const std::string& s)
+ {
+ return (details::imatch(lexer_.peek_next_token().value,s));
+ }
+
+ private:
+
+ generator_t lexer_;
+ token_t current_token_;
+ };
+#endif
+} // namespace lexertk
+
+#endif
diff --git a/contrib/libs/jinja2cpp/src/out_stream.h b/contrib/libs/jinja2cpp/src/out_stream.h
new file mode 100644
index 0000000000..8e3f001ba9
--- /dev/null
+++ b/contrib/libs/jinja2cpp/src/out_stream.h
@@ -0,0 +1,43 @@
+#ifndef JINJA2CPP_SRC_OUT_STREAM_H
+#define JINJA2CPP_SRC_OUT_STREAM_H
+
+#include "internal_value.h"
+
+#include <functional>
+#include <iostream>
+#include <sstream>
+
+namespace jinja2
+{
+class OutStream
+{
+public:
+ struct StreamWriter
+ {
+ virtual ~StreamWriter() {}
+
+ virtual void WriteBuffer(const void* ptr, size_t length) = 0;
+ virtual void WriteValue(const InternalValue &val) = 0;
+ };
+
+ OutStream(std::function<StreamWriter*()> writerGetter)
+ : m_writerGetter(std::move(writerGetter))
+ {}
+
+ void WriteBuffer(const void* ptr, size_t length)
+ {
+ m_writerGetter()->WriteBuffer(ptr, length);
+ }
+
+ void WriteValue(const InternalValue& val)
+ {
+ m_writerGetter()->WriteValue(val);
+ }
+
+private:
+ std::function<StreamWriter*()> m_writerGetter;
+};
+
+} // namespace jinja2
+
+#endif // JINJA2CPP_SRC_OUT_STREAM_H
diff --git a/contrib/libs/jinja2cpp/src/render_context.h b/contrib/libs/jinja2cpp/src/render_context.h
new file mode 100644
index 0000000000..f6edcf444e
--- /dev/null
+++ b/contrib/libs/jinja2cpp/src/render_context.h
@@ -0,0 +1,182 @@
+#ifndef JINJA2CPP_SRC_RENDER_CONTEXT_H
+#define JINJA2CPP_SRC_RENDER_CONTEXT_H
+
+#include "internal_value.h"
+#include <jinja2cpp/error_info.h>
+#include <jinja2cpp/utils/i_comparable.h>
+
+#include <contrib/restricted/expected-lite/include/nonstd/expected.hpp>
+
+#include <list>
+#include <deque>
+
+namespace jinja2
+{
+template<typename CharT>
+class TemplateImpl;
+
+struct IRendererCallback : IComparable
+{
+ virtual ~IRendererCallback() {}
+ virtual TargetString GetAsTargetString(const InternalValue& val) = 0;
+ virtual OutStream GetStreamOnString(TargetString& str) = 0;
+ virtual std::variant<EmptyValue,
+ nonstd::expected<std::shared_ptr<TemplateImpl<char>>, ErrorInfo>,
+ nonstd::expected<std::shared_ptr<TemplateImpl<wchar_t>>, ErrorInfoW>> LoadTemplate(const std::string& fileName) const = 0;
+ virtual std::variant<EmptyValue,
+ nonstd::expected<std::shared_ptr<TemplateImpl<char>>, ErrorInfo>,
+ nonstd::expected<std::shared_ptr<TemplateImpl<wchar_t>>, ErrorInfoW>> LoadTemplate(const InternalValue& fileName) const = 0;
+ virtual void ThrowRuntimeError(ErrorCode code, ValuesList extraParams) = 0;
+};
+
+class RenderContext
+{
+public:
+ RenderContext(const InternalValueMap& extValues, const InternalValueMap& globalValues, IRendererCallback* rendererCallback)
+ : m_rendererCallback(rendererCallback)
+ {
+ m_externalScope = &extValues;
+ m_globalScope = &globalValues;
+ EnterScope();
+ (*m_currentScope)["self"] = CreateMapAdapter(InternalValueMap());
+ }
+
+ RenderContext(const RenderContext& other)
+ : m_rendererCallback(other.m_rendererCallback)
+ , m_externalScope(other.m_externalScope)
+ , m_globalScope(other.m_globalScope)
+ , m_boundScope(other.m_boundScope)
+ , m_scopes(other.m_scopes)
+ {
+ m_currentScope = &m_scopes.back();
+ }
+
+ InternalValueMap& EnterScope()
+ {
+ m_scopes.push_back(InternalValueMap());
+ m_currentScope = &m_scopes.back();
+ return *m_currentScope;
+ }
+
+ void ExitScope()
+ {
+ m_scopes.pop_back();
+ if (!m_scopes.empty())
+ m_currentScope = &m_scopes.back();
+ else
+ m_currentScope = nullptr;
+ }
+
+ auto FindValue(const std::string& val, bool& found) const
+ {
+ auto finder = [&val, &found](auto& map) mutable
+ {
+ auto p = map.find(val);
+ if (p != map.end())
+ found = true;
+
+ return p;
+ };
+
+ if (m_boundScope)
+ {
+ auto valP = finder(*m_boundScope);
+ if (found)
+ return valP;
+ }
+
+ for (auto p = m_scopes.rbegin(); p != m_scopes.rend(); ++ p)
+ {
+ auto valP = finder(*p);
+ if (found)
+ return valP;
+ }
+
+ auto valP = finder(*m_externalScope);
+ if (found)
+ return valP;
+
+ return finder(*m_globalScope);
+ }
+
+ auto& GetCurrentScope() const
+ {
+ return *m_currentScope;
+ }
+
+ auto& GetCurrentScope()
+ {
+ return *m_currentScope;
+ }
+ auto& GetGlobalScope()
+ {
+ return m_scopes.front();
+ }
+ auto GetRendererCallback()
+ {
+ return m_rendererCallback;
+ }
+ RenderContext Clone(bool includeCurrentContext) const
+ {
+ if (!includeCurrentContext)
+ return RenderContext(m_emptyScope, *m_globalScope, m_rendererCallback);
+
+ return RenderContext(*this);
+ }
+
+ void BindScope(InternalValueMap* scope)
+ {
+ m_boundScope = scope;
+ }
+
+ bool IsEqual(const RenderContext& other) const
+ {
+ if (!IsEqual(m_rendererCallback, other.m_rendererCallback))
+ return false;
+ if (!IsEqual(this->m_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:
+ IRendererCallback* m_rendererCallback{};
+ InternalValueMap* m_currentScope{};
+ const InternalValueMap* m_externalScope{};
+ const InternalValueMap* m_globalScope{};
+ const InternalValueMap* m_boundScope{};
+ InternalValueMap m_emptyScope;
+ std::deque<InternalValueMap> m_scopes;
+};
+} // namespace jinja2
+
+#endif // JINJA2CPP_SRC_RENDER_CONTEXT_H
diff --git a/contrib/libs/jinja2cpp/src/renderer.h b/contrib/libs/jinja2cpp/src/renderer.h
new file mode 100644
index 0000000000..9c08d2379b
--- /dev/null
+++ b/contrib/libs/jinja2cpp/src/renderer.h
@@ -0,0 +1,131 @@
+#ifndef JINJA2CPP_SRC_RENDERER_H
+#define JINJA2CPP_SRC_RENDERER_H
+
+#include "out_stream.h"
+#include "lexertk.h"
+#include "expression_evaluator.h"
+#include "render_context.h"
+#include "ast_visitor.h"
+
+#include <jinja2cpp/value.h>
+#include <jinja2cpp/utils/i_comparable.h>
+
+#include <iostream>
+#include <string>
+#include <vector>
+#include <memory>
+
+namespace jinja2
+{
+class IRendererBase : public virtual IComparable
+{
+public:
+ virtual ~IRendererBase() = default;
+ virtual void Render(OutStream& os, RenderContext& values) = 0;
+};
+
+class VisitableRendererBase : public IRendererBase, public VisitableStatement
+{
+};
+
+using RendererPtr = std::shared_ptr<IRendererBase>;
+
+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
+{
+public:
+ VISITABLE_STATEMENT();
+
+ void AddRenderer(RendererPtr r)
+ {
+ m_renderers.push_back(std::move(r));
+ }
+ void Render(OutStream& os, RenderContext& values) override
+ {
+ for (auto& r : m_renderers)
+ r->Render(os, values);
+ }
+
+ bool IsEqual(const IComparable& other) const override
+ {
+ auto* val = dynamic_cast<const ComposedRenderer*>(&other);
+ if (!val)
+ return false;
+ return m_renderers == val->m_renderers;
+ }
+
+private:
+ std::vector<RendererPtr> m_renderers;
+};
+
+class RawTextRenderer : public VisitableRendererBase
+{
+public:
+ VISITABLE_STATEMENT();
+
+ RawTextRenderer(const void* ptr, size_t len)
+ : m_ptr(ptr)
+ , m_length(len)
+ {
+ }
+
+ void Render(OutStream& os, RenderContext&) override
+ {
+ os.WriteBuffer(m_ptr, m_length);
+ }
+
+ bool IsEqual(const IComparable& other) const override
+ {
+ auto* val = dynamic_cast<const RawTextRenderer*>(&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{};
+};
+
+class ExpressionRenderer : public VisitableRendererBase
+{
+public:
+ VISITABLE_STATEMENT();
+
+ explicit ExpressionRenderer(ExpressionEvaluatorPtr<> expr)
+ : m_expression(std::move(expr))
+ {
+ }
+
+ void Render(OutStream& os, RenderContext& values) override
+ {
+ m_expression->Render(os, values);
+ }
+
+ bool IsEqual(const IComparable& other) const override
+ {
+ auto* val = dynamic_cast<const ExpressionRenderer*>(&other);
+ if (!val)
+ return false;
+ return m_expression == val->m_expression;
+ }
+private:
+ ExpressionEvaluatorPtr<> m_expression;
+};
+} // namespace jinja2
+
+#endif // JINJA2CPP_SRC_RENDERER_H
diff --git a/contrib/libs/jinja2cpp/src/robin_hood.h b/contrib/libs/jinja2cpp/src/robin_hood.h
new file mode 100644
index 0000000000..b4e0fbc56a
--- /dev/null
+++ b/contrib/libs/jinja2cpp/src/robin_hood.h
@@ -0,0 +1,2544 @@
+// ______ _____ ______ _________
+// ______________ ___ /_ ___(_)_______ ___ /_ ______ ______ ______ /
+// __ ___/_ __ \__ __ \__ / __ __ \ __ __ \_ __ \_ __ \_ __ /
+// _ / / /_/ /_ /_/ /_ / _ / / / _ / / // /_/ // /_/ // /_/ /
+// /_/ \____/ /_.___/ /_/ /_/ /_/ ________/_/ /_/ \____/ \____/ \__,_/
+// _/_____/
+//
+// 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 <http://opensource.org/licenses/MIT>.
+// SPDX-License-Identifier: MIT
+// Copyright (c) 2018-2021 Martin Ankerl <http://martin.ankerl.com>
+//
+// 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 ROBIN_HOOD_H_INCLUDED
+#define ROBIN_HOOD_H_INCLUDED
+
+// 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 5 // for backwards-compatible bug fixes
+
+#include <algorithm>
+#include <cstdlib>
+#include <cstring>
+#include <functional>
+#include <limits>
+#include <memory> // only to support hash of smart pointers
+#include <stdexcept>
+#include <string>
+#include <type_traits>
+#include <utility>
+#if __cplusplus >= 201703L
+# include <string_view>
+#endif
+
+// #define ROBIN_HOOD_LOG_ENABLED
+#ifdef ROBIN_HOOD_LOG_ENABLED
+# include <iostream>
+# define ROBIN_HOOD_LOG(...) \
+ std::cout << __FUNCTION__ << "@" << __LINE__ << ": " << __VA_ARGS__ << std::endl;
+#else
+# define ROBIN_HOOD_LOG(x)
+#endif
+
+// #define ROBIN_HOOD_TRACE_ENABLED
+#ifdef ROBIN_HOOD_TRACE_ENABLED
+# include <iostream>
+# 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 <iostream>
+# 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()
+
+// mark unused members with this macro
+#define ROBIN_HOOD_UNUSED(identifier)
+
+// bitness
+#if SIZE_MAX == UINT32_MAX
+# define ROBIN_HOOD_PRIVATE_DEFINITION_BITNESS() 32
+#elif SIZE_MAX == UINT64_MAX
+# define ROBIN_HOOD_PRIVATE_DEFINITION_BITNESS() 64
+#else
+# error Unsupported bitness
+#endif
+
+// endianess
+#ifdef _MSC_VER
+# define ROBIN_HOOD_PRIVATE_DEFINITION_LITTLE_ENDIAN() 1
+# define ROBIN_HOOD_PRIVATE_DEFINITION_BIG_ENDIAN() 0
+#else
+# define ROBIN_HOOD_PRIVATE_DEFINITION_LITTLE_ENDIAN() \
+ (__BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__)
+# define ROBIN_HOOD_PRIVATE_DEFINITION_BIG_ENDIAN() (__BYTE_ORDER__ == __ORDER_BIG_ENDIAN__)
+#endif
+
+// inline
+#ifdef _MSC_VER
+# define ROBIN_HOOD_PRIVATE_DEFINITION_NOINLINE() __declspec(noinline)
+#else
+# define ROBIN_HOOD_PRIVATE_DEFINITION_NOINLINE() __attribute__((noinline))
+#endif
+
+// exceptions
+#if !defined(__cpp_exceptions) && !defined(__EXCEPTIONS) && !defined(_CPPUNWIND)
+# define ROBIN_HOOD_PRIVATE_DEFINITION_HAS_EXCEPTIONS() 0
+#else
+# 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 <intrin.h>
+# 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<int>(index) \
+ : ROBIN_HOOD(BITNESS); \
+ }(x)
+# else
+# 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
+#endif
+
+// fallthrough
+#ifndef __has_cpp_attribute // For backwards compatibility
+# define __has_cpp_attribute(x) 0
+#endif
+#if __has_cpp_attribute(clang::fallthrough)
+# define ROBIN_HOOD_PRIVATE_DEFINITION_FALLTHROUGH() [[clang::fallthrough]]
+#elif __has_cpp_attribute(gnu::fallthrough)
+# define ROBIN_HOOD_PRIVATE_DEFINITION_FALLTHROUGH() [[gnu::fallthrough]]
+#else
+# define ROBIN_HOOD_PRIVATE_DEFINITION_FALLTHROUGH()
+#endif
+
+// likely/unlikely
+#ifdef _MSC_VER
+# define ROBIN_HOOD_LIKELY(condition) condition
+# define ROBIN_HOOD_UNLIKELY(condition) condition
+#else
+# define ROBIN_HOOD_LIKELY(condition) __builtin_expect(condition, 1)
+# 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 && !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
+#endif
+
+// helpers for C++ versions, see https://gcc.gnu.org/onlinedocs/cpp/Standard-Predefined-Macros.html
+#define ROBIN_HOOD_PRIVATE_DEFINITION_CXX() __cplusplus
+#define ROBIN_HOOD_PRIVATE_DEFINITION_CXX98() 199711L
+#define ROBIN_HOOD_PRIVATE_DEFINITION_CXX11() 201103L
+#define ROBIN_HOOD_PRIVATE_DEFINITION_CXX14() 201402L
+#define ROBIN_HOOD_PRIVATE_DEFINITION_CXX17() 201703L
+
+#if ROBIN_HOOD(CXX) >= ROBIN_HOOD(CXX17)
+# define ROBIN_HOOD_PRIVATE_DEFINITION_NODISCARD() [[nodiscard]]
+#else
+# define ROBIN_HOOD_PRIVATE_DEFINITION_NODISCARD()
+#endif
+
+namespace robin_hood {
+
+#if ROBIN_HOOD(CXX) >= ROBIN_HOOD(CXX14)
+# define ROBIN_HOOD_STD std
+#else
+
+// c++11 compatibility layer
+namespace ROBIN_HOOD_STD {
+template <class T>
+struct alignment_of
+ : std::integral_constant<std::size_t, alignof(typename std::remove_all_extents<T>::type)> {};
+
+template <class T, T... Ints>
+class integer_sequence {
+public:
+ using value_type = T;
+ static_assert(std::is_integral<value_type>::value, "not integral type");
+ static constexpr std::size_t size() noexcept {
+ return sizeof...(Ints);
+ }
+};
+template <std::size_t... Inds>
+using index_sequence = integer_sequence<std::size_t, Inds...>;
+
+namespace detail_ {
+template <class T, T Begin, T End, bool>
+struct IntSeqImpl {
+ using TValue = T;
+ static_assert(std::is_integral<TValue>::value, "not integral type");
+ static_assert(Begin >= 0 && Begin < End, "unexpected argument (Begin<0 || Begin<=End)");
+
+ template <class, class>
+ struct IntSeqCombiner;
+
+ template <TValue... Inds0, TValue... Inds1>
+ struct IntSeqCombiner<integer_sequence<TValue, Inds0...>, integer_sequence<TValue, Inds1...>> {
+ using TResult = integer_sequence<TValue, Inds0..., Inds1...>;
+ };
+
+ using TResult =
+ typename IntSeqCombiner<typename IntSeqImpl<TValue, Begin, Begin + (End - Begin) / 2,
+ (End - Begin) / 2 == 1>::TResult,
+ typename IntSeqImpl<TValue, Begin + (End - Begin) / 2, End,
+ (End - Begin + 1) / 2 == 1>::TResult>::TResult;
+};
+
+template <class T, T Begin>
+struct IntSeqImpl<T, Begin, Begin, false> {
+ using TValue = T;
+ static_assert(std::is_integral<TValue>::value, "not integral type");
+ static_assert(Begin >= 0, "unexpected argument (Begin<0)");
+ using TResult = integer_sequence<TValue>;
+};
+
+template <class T, T Begin, T End>
+struct IntSeqImpl<T, Begin, End, true> {
+ using TValue = T;
+ static_assert(std::is_integral<TValue>::value, "not integral type");
+ static_assert(Begin >= 0, "unexpected argument (Begin<0)");
+ using TResult = integer_sequence<TValue, Begin>;
+};
+} // namespace detail_
+
+template <class T, T N>
+using make_integer_sequence = typename detail_::IntSeqImpl<T, 0, N, (N - 0) == 1>::TResult;
+
+template <std::size_t N>
+using make_index_sequence = make_integer_sequence<std::size_t, N>;
+
+template <class... T>
+using index_sequence_for = make_index_sequence<sizeof...(T)>;
+
+} // namespace ROBIN_HOOD_STD
+
+#endif
+
+namespace detail {
+
+// make sure we static_cast to the correct type for hash_int
+#if ROBIN_HOOD(BITNESS) == 64
+using SizeT = uint64_t;
+#else
+using SizeT = uint32_t;
+#endif
+
+template <typename T>
+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!
+template <typename T>
+inline T reinterpret_cast_no_cast_align_warning(void* ptr) noexcept {
+ return reinterpret_cast<T>(ptr);
+}
+
+template <typename T>
+inline T reinterpret_cast_no_cast_align_warning(void const* ptr) noexcept {
+ return reinterpret_cast<T>(ptr);
+}
+
+// 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 <typename E, typename... 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>(args)...);
+}
+#else
+ void doThrow(Args&&... ROBIN_HOOD_UNUSED(args) /*unused*/) {
+ abort();
+}
+#endif
+
+template <typename E, typename T, typename... Args>
+T* assertNotNull(T* t, Args&&... args) {
+ if (ROBIN_HOOD_UNLIKELY(nullptr == t)) {
+ doThrow<E>(std::forward<Args>(args)...);
+ }
+ return t;
+}
+
+template <typename T>
+inline T unaligned_load(void const* ptr) noexcept {
+ // using memcpy so we don't get into unaligned load problems.
+ // compiler should optimize this very well anyways.
+ T t;
+ std::memcpy(&t, ptr, sizeof(T));
+ return t;
+}
+
+// Allocates bulks of memory for objects of type T. This deallocates the memory in the destructor,
+// and keeps a linked list of the allocated memory around. Overhead per allocation is the size of a
+// pointer.
+template <typename T, size_t MinNumAllocs = 4, size_t MaxNumAllocs = 256>
+class BulkPoolAllocator {
+public:
+ BulkPoolAllocator() noexcept = default;
+
+ // does not copy anything, just creates a new allocator.
+ BulkPoolAllocator(const BulkPoolAllocator& ROBIN_HOOD_UNUSED(o) /*unused*/) noexcept
+ : mHead(nullptr)
+ , mListForFree(nullptr) {}
+
+ BulkPoolAllocator(BulkPoolAllocator&& o) noexcept
+ : mHead(o.mHead)
+ , mListForFree(o.mListForFree) {
+ o.mListForFree = nullptr;
+ o.mHead = nullptr;
+ }
+
+ BulkPoolAllocator& operator=(BulkPoolAllocator&& o) noexcept {
+ reset();
+ mHead = o.mHead;
+ mListForFree = o.mListForFree;
+ o.mListForFree = nullptr;
+ o.mHead = nullptr;
+ return *this;
+ }
+
+ BulkPoolAllocator&
+ // NOLINTNEXTLINE(bugprone-unhandled-self-assignment,cert-oop54-cpp)
+ operator=(const BulkPoolAllocator& ROBIN_HOOD_UNUSED(o) /*unused*/) noexcept {
+ // does not do anything
+ return *this;
+ }
+
+ ~BulkPoolAllocator() noexcept {
+ reset();
+ }
+
+ // Deallocates all allocated memory.
+ void reset() noexcept {
+ while (mListForFree) {
+ T* tmp = *mListForFree;
+ ROBIN_HOOD_LOG("std::free")
+ std::free(mListForFree);
+ mListForFree = reinterpret_cast_no_cast_align_warning<T**>(tmp);
+ }
+ mHead = nullptr;
+ }
+
+ // allocates, but does NOT initialize. Use in-place new constructor, e.g.
+ // T* obj = pool.allocate();
+ // ::new (static_cast<void*>(obj)) T();
+ T* allocate() {
+ T* tmp = mHead;
+ if (!tmp) {
+ tmp = performAllocation();
+ }
+
+ mHead = *reinterpret_cast_no_cast_align_warning<T**>(tmp);
+ return tmp;
+ }
+
+ // does not actually deallocate but puts it in store.
+ // make sure you have already called the destructor! e.g. with
+ // obj->~T();
+ // pool.deallocate(obj);
+ void deallocate(T* obj) noexcept {
+ *reinterpret_cast_no_cast_align_warning<T**>(obj) = mHead;
+ mHead = obj;
+ }
+
+ // Adds an already allocated block of memory to the allocator. This allocator is from now on
+ // responsible for freeing the data (with free()). If the provided data is not large enough to
+ // make use of, it is immediately freed. Otherwise it is reused and freed in the destructor.
+ void addOrFree(void* ptr, const size_t numBytes) noexcept {
+ // calculate number of available elements in ptr
+ if (numBytes < ALIGNMENT + ALIGNED_SIZE) {
+ // not enough data for at least one element. Free and return.
+ ROBIN_HOOD_LOG("std::free")
+ std::free(ptr);
+ } else {
+ ROBIN_HOOD_LOG("add to buffer")
+ add(ptr, numBytes);
+ }
+ }
+
+ void swap(BulkPoolAllocator<T, MinNumAllocs, MaxNumAllocs>& other) noexcept {
+ using std::swap;
+ swap(mHead, other.mHead);
+ swap(mListForFree, other.mListForFree);
+ }
+
+private:
+ // iterates the list of allocated memory to calculate how many to alloc next.
+ // Recalculating this each time saves us a size_t member.
+ // This ignores the fact that memory blocks might have been added manually with addOrFree. In
+ // practice, this should not matter much.
+ ROBIN_HOOD(NODISCARD) size_t calcNumElementsToAlloc() const noexcept {
+ auto tmp = mListForFree;
+ size_t numAllocs = MinNumAllocs;
+
+ while (numAllocs * 2 <= MaxNumAllocs && tmp) {
+ auto x = reinterpret_cast<T***>(tmp);
+ tmp = *x;
+ numAllocs *= 2;
+ }
+
+ return numAllocs;
+ }
+
+ // WARNING: Underflow if numBytes < ALIGNMENT! This is guarded in addOrFree().
+ void add(void* ptr, const size_t numBytes) noexcept {
+ const size_t numElements = (numBytes - ALIGNMENT) / ALIGNED_SIZE;
+
+ auto data = reinterpret_cast<T**>(ptr);
+
+ // link free list
+ auto x = reinterpret_cast<T***>(data);
+ *x = mListForFree;
+ mListForFree = data;
+
+ // create linked list for newly allocated data
+ auto* const headT =
+ reinterpret_cast_no_cast_align_warning<T*>(reinterpret_cast<char*>(ptr) + ALIGNMENT);
+
+ auto* const head = reinterpret_cast<char*>(headT);
+
+ // Visual Studio compiler automatically unrolls this loop, which is pretty cool
+ for (size_t i = 0; i < numElements; ++i) {
+ *reinterpret_cast_no_cast_align_warning<char**>(head + i * ALIGNED_SIZE) =
+ head + (i + 1) * ALIGNED_SIZE;
+ }
+
+ // last one points to 0
+ *reinterpret_cast_no_cast_align_warning<T**>(head + (numElements - 1) * ALIGNED_SIZE) =
+ mHead;
+ mHead = headT;
+ }
+
+ // Called when no memory is available (mHead == 0).
+ // Don't inline this slow path.
+ ROBIN_HOOD(NOINLINE) T* performAllocation() {
+ size_t const numElementsToAlloc = calcNumElementsToAlloc();
+
+ // alloc new memory: [prev |T, T, ... T]
+ size_t const bytes = ALIGNMENT + ALIGNED_SIZE * numElementsToAlloc;
+ ROBIN_HOOD_LOG("std::malloc " << bytes << " = " << ALIGNMENT << " + " << ALIGNED_SIZE
+ << " * " << numElementsToAlloc)
+ add(assertNotNull<std::bad_alloc>(std::malloc(bytes)), bytes);
+ return mHead;
+ }
+
+ // enforce byte alignment of the T's
+#if ROBIN_HOOD(CXX) >= ROBIN_HOOD(CXX14)
+ static constexpr size_t ALIGNMENT =
+ (std::max)(std::alignment_of<T>::value, std::alignment_of<T*>::value);
+#else
+ static const size_t ALIGNMENT =
+ (ROBIN_HOOD_STD::alignment_of<T>::value > ROBIN_HOOD_STD::alignment_of<T*>::value)
+ ? ROBIN_HOOD_STD::alignment_of<T>::value
+ : +ROBIN_HOOD_STD::alignment_of<T*>::value; // the + is for walkarround
+#endif
+
+ static constexpr size_t ALIGNED_SIZE = ((sizeof(T) - 1) / ALIGNMENT + 1) * ALIGNMENT;
+
+ static_assert(MinNumAllocs >= 1, "MinNumAllocs");
+ static_assert(MaxNumAllocs >= MinNumAllocs, "MaxNumAllocs");
+ static_assert(ALIGNED_SIZE >= sizeof(T*), "ALIGNED_SIZE");
+ static_assert(0 == (ALIGNED_SIZE % sizeof(T*)), "ALIGNED_SIZE mod");
+ static_assert(ALIGNMENT >= sizeof(T*), "ALIGNMENT");
+
+ T* mHead{nullptr};
+ T** mListForFree{nullptr};
+};
+
+template <typename T, size_t MinSize, size_t MaxSize, bool IsFlat>
+struct NodeAllocator;
+
+// dummy allocator that does nothing
+template <typename T, size_t MinSize, size_t MaxSize>
+struct NodeAllocator<T, MinSize, MaxSize, true> {
+
+ // we are not using the data, so just free it.
+ void addOrFree(void* ptr, size_t ROBIN_HOOD_UNUSED(numBytes) /*unused*/) noexcept {
+ ROBIN_HOOD_LOG("std::free")
+ std::free(ptr);
+ }
+};
+
+template <typename T, size_t MinSize, size_t MaxSize>
+struct NodeAllocator<T, MinSize, MaxSize, false> : public BulkPoolAllocator<T, MinSize, MaxSize> {};
+
+// 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 <typename T>
+struct nothrow {
+ static const bool value = noexcept(swap(std::declval<T&>(), std::declval<T&>()));
+};
+#else
+template <typename T>
+struct nothrow {
+ static const bool value = std::is_nothrow_swappable<T>::value;
+};
+#endif
+} // namespace swappable
+
+} // namespace detail
+
+struct is_transparent_tag {};
+
+// A custom pair implementation is used in the map because std::pair is not is_trivially_copyable,
+// which means it would not be allowed to be used in std::memcpy. This struct is copyable, which is
+// also tested.
+template <typename T1, typename T2>
+struct pair {
+ using first_type = T1;
+ using second_type = T2;
+
+ template <typename U1 = T1, typename U2 = T2,
+ typename = typename std::enable_if<std::is_default_constructible<U1>::value &&
+ std::is_default_constructible<U2>::value>::type>
+ constexpr pair() noexcept(noexcept(U1()) && noexcept(U2()))
+ : first()
+ , second() {}
+
+ // pair constructors are explicit so we don't accidentally call this ctor when we don't have to.
+ explicit constexpr pair(std::pair<T1, T2> const& o) noexcept(
+ noexcept(T1(std::declval<T1 const&>())) && noexcept(T2(std::declval<T2 const&>())))
+ : first(o.first)
+ , 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<T1, T2>&& o) noexcept(noexcept(
+ T1(std::move(std::declval<T1&&>()))) && noexcept(T2(std::move(std::declval<T2&&>()))))
+ : first(std::move(o.first))
+ , second(std::move(o.second)) {}
+
+ constexpr pair(T1&& a, T2&& b) noexcept(noexcept(
+ T1(std::move(std::declval<T1&&>()))) && noexcept(T2(std::move(std::declval<T2&&>()))))
+ : first(std::move(a))
+ , second(std::move(b)) {}
+
+ template <typename U1, typename U2>
+ constexpr pair(U1&& a, U2&& b) noexcept(noexcept(T1(std::forward<U1>(
+ std::declval<U1&&>()))) && noexcept(T2(std::forward<U2>(std::declval<U2&&>()))))
+ : first(std::forward<U1>(a))
+ , second(std::forward<U2>(b)) {}
+
+ template <typename... U1, typename... U2>
+ // 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<U1...> a,
+ std::tuple<U2...>
+ b) noexcept(noexcept(pair(std::declval<std::tuple<U1...>&>(),
+ std::declval<std::tuple<U2...>&>(),
+ ROBIN_HOOD_STD::index_sequence_for<U1...>(),
+ ROBIN_HOOD_STD::index_sequence_for<U2...>())))
+ : pair(a, b, ROBIN_HOOD_STD::index_sequence_for<U1...>(),
+ ROBIN_HOOD_STD::index_sequence_for<U2...>()) {
+ }
+
+ // constructor called from the std::piecewise_construct_t ctor
+ template <typename... U1, size_t... I1, typename... U2, size_t... I2>
+ pair(std::tuple<U1...>& a, std::tuple<U2...>& b, ROBIN_HOOD_STD::index_sequence<I1...> /*unused*/, ROBIN_HOOD_STD::index_sequence<I2...> /*unused*/) noexcept(
+ noexcept(T1(std::forward<U1>(std::get<I1>(
+ std::declval<std::tuple<
+ U1...>&>()))...)) && noexcept(T2(std::
+ forward<U2>(std::get<I2>(
+ std::declval<std::tuple<U2...>&>()))...)))
+ : first(std::forward<U1>(std::get<I1>(a))...)
+ , second(std::forward<U2>(std::get<I2>(b))...) {
+ // make visual studio compiler happy about warning about unused a & b.
+ // Visual studio's pair implementation disables warning 4100.
+ (void)a;
+ (void)b;
+ }
+
+ void swap(pair<T1, T2>& o) noexcept((detail::swappable::nothrow<T1>::value) &&
+ (detail::swappable::nothrow<T2>::value)) {
+ using std::swap;
+ swap(first, o.first);
+ swap(second, o.second);
+ }
+
+ T1 first; // NOLINT(misc-non-private-member-variables-in-classes)
+ T2 second; // NOLINT(misc-non-private-member-variables-in-classes)
+};
+
+template <typename A, typename B>
+inline void swap(pair<A, B>& a, pair<A, B>& b) noexcept(
+ noexcept(std::declval<pair<A, B>&>().swap(std::declval<pair<A, B>&>()))) {
+ a.swap(b);
+}
+
+template <typename A, typename B>
+inline constexpr bool operator==(pair<A, B> const& x, pair<A, B> const& y) {
+ return (x.first == y.first) && (x.second == y.second);
+}
+template <typename A, typename B>
+inline constexpr bool operator!=(pair<A, B> const& x, pair<A, B> const& y) {
+ return !(x == y);
+}
+template <typename A, typename B>
+inline constexpr bool operator<(pair<A, B> const& x, pair<A, B> const& y) noexcept(noexcept(
+ std::declval<A const&>() < std::declval<A const&>()) && noexcept(std::declval<B const&>() <
+ std::declval<B const&>())) {
+ return x.first < y.first || (!(y.first < x.first) && x.second < y.second);
+}
+template <typename A, typename B>
+inline constexpr bool operator>(pair<A, B> const& x, pair<A, B> const& y) {
+ return y < x;
+}
+template <typename A, typename B>
+inline constexpr bool operator<=(pair<A, B> const& x, pair<A, B> const& y) {
+ return !(x > y);
+}
+template <typename A, typename B>
+inline constexpr bool operator>=(pair<A, B> const& x, pair<A, B> 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* const data64 = static_cast<uint64_t const*>(ptr);
+ uint64_t h = seed ^ (len * m);
+
+ size_t const n_blocks = len / 8;
+ for (size_t i = 0; i < n_blocks; ++i) {
+ auto k = detail::unaligned_load<uint64_t>(data64 + i);
+
+ k *= m;
+ k ^= k >> r;
+ k *= m;
+
+ h ^= k;
+ h *= m;
+ }
+
+ auto const* const data8 = reinterpret_cast<uint8_t const*>(data64 + n_blocks);
+ switch (len & 7U) {
+ case 7:
+ h ^= static_cast<uint64_t>(data8[6]) << 48U;
+ ROBIN_HOOD(FALLTHROUGH); // FALLTHROUGH
+ case 6:
+ h ^= static_cast<uint64_t>(data8[5]) << 40U;
+ ROBIN_HOOD(FALLTHROUGH); // FALLTHROUGH
+ case 5:
+ h ^= static_cast<uint64_t>(data8[4]) << 32U;
+ ROBIN_HOOD(FALLTHROUGH); // FALLTHROUGH
+ case 4:
+ h ^= static_cast<uint64_t>(data8[3]) << 24U;
+ ROBIN_HOOD(FALLTHROUGH); // FALLTHROUGH
+ case 3:
+ h ^= static_cast<uint64_t>(data8[2]) << 16U;
+ ROBIN_HOOD(FALLTHROUGH); // FALLTHROUGH
+ case 2:
+ h ^= static_cast<uint64_t>(data8[1]) << 8U;
+ ROBIN_HOOD(FALLTHROUGH); // FALLTHROUGH
+ case 1:
+ h ^= static_cast<uint64_t>(data8[0]);
+ h *= m;
+ ROBIN_HOOD(FALLTHROUGH); // FALLTHROUGH
+ default:
+ break;
+ }
+
+ 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<size_t>(h);
+}
+
+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<size_t>(x);
+}
+
+// A thin wrapper around std::hash, performing an additional simple mixing step of the result.
+template <typename T, typename Enable = void>
+struct hash : public std::hash<T> {
+ size_t operator()(T const& obj) const
+ noexcept(noexcept(std::declval<std::hash<T>>().operator()(std::declval<T const&>()))) {
+ // call base hash
+ auto result = std::hash<T>::operator()(obj);
+ // return mixed of that, to be save against identity has
+ return hash_int(static_cast<detail::SizeT>(result));
+ }
+};
+
+template <typename CharT>
+struct hash<std::basic_string<CharT>> {
+ size_t operator()(std::basic_string<CharT> const& str) const noexcept {
+ return hash_bytes(str.data(), sizeof(CharT) * str.size());
+ }
+};
+
+#if ROBIN_HOOD(CXX) >= ROBIN_HOOD(CXX17)
+template <typename CharT>
+struct hash<std::basic_string_view<CharT>> {
+ size_t operator()(std::basic_string_view<CharT> const& sv) const noexcept {
+ return hash_bytes(sv.data(), sizeof(CharT) * sv.size());
+ }
+};
+#endif
+
+template <class T>
+struct hash<T*> {
+ size_t operator()(T* ptr) const noexcept {
+ return hash_int(reinterpret_cast<detail::SizeT>(ptr));
+ }
+};
+
+template <class T>
+struct hash<std::unique_ptr<T>> {
+ size_t operator()(std::unique_ptr<T> const& ptr) const noexcept {
+ return hash_int(reinterpret_cast<detail::SizeT>(ptr.get()));
+ }
+};
+
+template <class T>
+struct hash<std::shared_ptr<T>> {
+ size_t operator()(std::shared_ptr<T> const& ptr) const noexcept {
+ return hash_int(reinterpret_cast<detail::SizeT>(ptr.get()));
+ }
+};
+
+template <typename Enum>
+struct hash<Enum, typename std::enable_if<std::is_enum<Enum>::value>::type> {
+ size_t operator()(Enum e) const noexcept {
+ using Underlying = typename std::underlying_type<Enum>::type;
+ return hash<Underlying>{}(static_cast<Underlying>(e));
+ }
+};
+
+#define ROBIN_HOOD_HASH_INT(T) \
+ template <> \
+ struct hash<T> { \
+ size_t operator()(T const& obj) const noexcept { \
+ return hash_int(static_cast<uint64_t>(obj)); \
+ } \
+ }
+
+#if defined(__GNUC__) && !defined(__clang__)
+# pragma GCC diagnostic push
+# pragma GCC diagnostic ignored "-Wuseless-cast"
+#endif
+// see https://en.cppreference.com/w/cpp/utility/hash
+ROBIN_HOOD_HASH_INT(bool);
+ROBIN_HOOD_HASH_INT(char);
+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);
+ROBIN_HOOD_HASH_INT(unsigned int);
+ROBIN_HOOD_HASH_INT(long);
+ROBIN_HOOD_HASH_INT(long long);
+ROBIN_HOOD_HASH_INT(unsigned long);
+ROBIN_HOOD_HASH_INT(unsigned long long);
+#if defined(__GNUC__) && !defined(__clang__)
+# pragma GCC diagnostic pop
+#endif
+namespace detail {
+
+template <typename T>
+struct void_type {
+ using type = void;
+};
+
+template <typename T, typename = void>
+struct has_is_transparent : public std::false_type {};
+
+template <typename T>
+struct has_is_transparent<T, typename void_type<typename T::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 <typename T>
+struct WrapHash : public T {
+ WrapHash() = default;
+ explicit WrapHash(T const& o) noexcept(noexcept(T(std::declval<T const&>())))
+ : T(o) {}
+};
+
+template <typename T>
+struct WrapKeyEqual : public T {
+ WrapKeyEqual() = default;
+ explicit WrapKeyEqual(T const& o) noexcept(noexcept(T(std::declval<T const&>())))
+ : 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.
+//
+// This implementation uses the following memory layout:
+//
+// [Node, Node, ... Node | info, info, ... infoSentinel ]
+//
+// * Node: either a DataNode that directly has the std::pair<key, val> as member,
+// or a DataNode with a pointer to std::pair<key,val>. 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.
+//
+// * 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
+// corresponding node contains data. Set to 2 means the corresponding Node is filled, but it
+// 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.
+//
+// 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 <bool IsFlat, size_t MaxLoadFactor100, typename Key, typename T, typename Hash,
+ typename KeyEqual>
+class Table
+ : public WrapHash<Hash>,
+ public WrapKeyEqual<KeyEqual>,
+ detail::NodeAllocator<
+ typename std::conditional<
+ std::is_void<T>::value, Key,
+ robin_hood::pair<typename std::conditional<IsFlat, Key, Key const>::type, T>>::type,
+ 4, 16384, IsFlat> {
+public:
+ static constexpr bool is_flat = IsFlat;
+ static constexpr bool is_map = !std::is_void<T>::value;
+ static constexpr bool is_set = !is_map;
+ static constexpr bool is_transparent =
+ has_is_transparent<Hash>::value && has_is_transparent<KeyEqual>::value;
+
+ using key_type = Key;
+ using mapped_type = T;
+ using value_type = typename std::conditional<
+ is_set, Key,
+ robin_hood::pair<typename std::conditional<is_flat, Key, Key const>::type, T>>::type;
+ using size_type = size_t;
+ using hasher = Hash;
+ using key_equal = KeyEqual;
+ using Self = Table<IsFlat, MaxLoadFactor100, key_type, mapped_type, hasher, key_equal>;
+
+private:
+ static_assert(MaxLoadFactor100 > 10 && MaxLoadFactor100 < 100,
+ "MaxLoadFactor100 needs to be >10 && < 100");
+
+ using WHash = WrapHash<Hash>;
+ using WKeyEqual = WrapKeyEqual<KeyEqual>;
+
+ // 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 size_t InfoMask = InitialInfoInc - 1U;
+ static constexpr uint8_t InitialInfoHashShift = 0;
+ using DataPool = detail::NodeAllocator<value_type, 4, 16384, IsFlat>;
+
+ // type needs to be wider than uint8_t.
+ using InfoType = uint32_t;
+
+ // 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.
+ template <typename M, bool>
+ class DataNode {};
+
+ // Small: just allocate on the stack.
+ template <typename M>
+ class DataNode<M, true> final {
+ public:
+ template <typename... Args>
+ explicit DataNode(M& ROBIN_HOOD_UNUSED(map) /*unused*/, Args&&... args) noexcept(
+ noexcept(value_type(std::forward<Args>(args)...)))
+ : mData(std::forward<Args>(args)...) {}
+
+ DataNode(M& ROBIN_HOOD_UNUSED(map) /*unused*/, DataNode<M, true>&& n) noexcept(
+ std::is_nothrow_move_constructible<value_type>::value)
+ : mData(std::move(n.mData)) {}
+
+ // doesn't do anything
+ void destroy(M& ROBIN_HOOD_UNUSED(map) /*unused*/) noexcept {}
+ void destroyDoNotDeallocate() noexcept {}
+
+ value_type const* operator->() const noexcept {
+ return &mData;
+ }
+ value_type* operator->() noexcept {
+ return &mData;
+ }
+
+ const value_type& operator*() const noexcept {
+ return mData;
+ }
+
+ value_type& operator*() noexcept {
+ return mData;
+ }
+
+ template <typename VT = value_type>
+ ROBIN_HOOD(NODISCARD)
+ typename std::enable_if<is_map, typename VT::first_type&>::type getFirst() noexcept {
+ return mData.first;
+ }
+ template <typename VT = value_type>
+ ROBIN_HOOD(NODISCARD)
+ typename std::enable_if<is_set, VT&>::type getFirst() noexcept {
+ return mData;
+ }
+
+ template <typename VT = value_type>
+ ROBIN_HOOD(NODISCARD)
+ typename std::enable_if<is_map, typename VT::first_type const&>::type
+ getFirst() const noexcept {
+ return mData.first;
+ }
+ template <typename VT = value_type>
+ ROBIN_HOOD(NODISCARD)
+ typename std::enable_if<is_set, VT const&>::type getFirst() const noexcept {
+ return mData;
+ }
+
+ template <typename MT = mapped_type>
+ ROBIN_HOOD(NODISCARD)
+ typename std::enable_if<is_map, MT&>::type getSecond() noexcept {
+ return mData.second;
+ }
+
+ template <typename MT = mapped_type>
+ ROBIN_HOOD(NODISCARD)
+ typename std::enable_if<is_set, MT const&>::type getSecond() const noexcept {
+ return mData.second;
+ }
+
+ void swap(DataNode<M, true>& o) noexcept(
+ noexcept(std::declval<value_type>().swap(std::declval<value_type>()))) {
+ mData.swap(o.mData);
+ }
+
+ private:
+ value_type mData;
+ };
+
+ // big object: allocate on heap.
+ template <typename M>
+ class DataNode<M, false> {
+ public:
+ template <typename... Args>
+ explicit DataNode(M& map, Args&&... args)
+ : mData(map.allocate()) {
+ ::new (static_cast<void*>(mData)) value_type(std::forward<Args>(args)...);
+ }
+
+ DataNode(M& ROBIN_HOOD_UNUSED(map) /*unused*/, DataNode<M, false>&& n) noexcept
+ : mData(std::move(n.mData)) {}
+
+ void destroy(M& map) noexcept {
+ // don't deallocate, just put it into list of datapool.
+ mData->~value_type();
+ map.deallocate(mData);
+ }
+
+ void destroyDoNotDeallocate() noexcept {
+ mData->~value_type();
+ }
+
+ value_type const* operator->() const noexcept {
+ return mData;
+ }
+
+ value_type* operator->() noexcept {
+ return mData;
+ }
+
+ const value_type& operator*() const {
+ return *mData;
+ }
+
+ value_type& operator*() {
+ return *mData;
+ }
+
+ template <typename VT = value_type>
+ ROBIN_HOOD(NODISCARD)
+ typename std::enable_if<is_map, typename VT::first_type&>::type getFirst() noexcept {
+ return mData->first;
+ }
+ template <typename VT = value_type>
+ ROBIN_HOOD(NODISCARD)
+ typename std::enable_if<is_set, VT&>::type getFirst() noexcept {
+ return *mData;
+ }
+
+ template <typename VT = value_type>
+ ROBIN_HOOD(NODISCARD)
+ typename std::enable_if<is_map, typename VT::first_type const&>::type
+ getFirst() const noexcept {
+ return mData->first;
+ }
+ template <typename VT = value_type>
+ ROBIN_HOOD(NODISCARD)
+ typename std::enable_if<is_set, VT const&>::type getFirst() const noexcept {
+ return *mData;
+ }
+
+ template <typename MT = mapped_type>
+ ROBIN_HOOD(NODISCARD)
+ typename std::enable_if<is_map, MT&>::type getSecond() noexcept {
+ return mData->second;
+ }
+
+ template <typename MT = mapped_type>
+ ROBIN_HOOD(NODISCARD)
+ typename std::enable_if<is_map, MT const&>::type getSecond() const noexcept {
+ return mData->second;
+ }
+
+ void swap(DataNode<M, false>& o) noexcept {
+ using std::swap;
+ swap(mData, o.mData);
+ }
+
+ private:
+ value_type* mData;
+ };
+
+ using Node = DataNode<Self, IsFlat>;
+
+ // 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 <typename Q = mapped_type>
+ ROBIN_HOOD(NODISCARD)
+ typename std::enable_if<!std::is_void<Q>::value, key_type const&>::type
+ getFirstConst(value_type const& vt) const noexcept {
+ return vt.first;
+ }
+
+ // Cloner //////////////////////////////////////////////////////////
+
+ template <typename M, bool UseMemcpy>
+ struct Cloner;
+
+ // fast path: Just copy data, without allocating anything.
+ template <typename M>
+ struct Cloner<M, true> {
+ void operator()(M const& source, M& target) const {
+ auto const* const src = reinterpret_cast<char const*>(source.mKeyVals);
+ auto* tgt = reinterpret_cast<char*>(target.mKeyVals);
+ auto const numElementsWithBuffer = target.calcNumElementsWithBuffer(target.mMask + 1);
+ std::copy(src, src + target.calcNumBytesTotal(numElementsWithBuffer), tgt);
+ }
+ };
+
+ template <typename M>
+ struct Cloner<M, false> {
+ void operator()(M const& s, M& t) const {
+ 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 < numElementsWithBuffer; ++i) {
+ if (t.mInfo[i]) {
+ ::new (static_cast<void*>(t.mKeyVals + i)) Node(t, *s.mKeyVals[i]);
+ }
+ }
+ }
+ };
+
+ // Destroyer ///////////////////////////////////////////////////////
+
+ template <typename M, bool IsFlatAndTrivial>
+ struct Destroyer {};
+
+ template <typename M>
+ struct Destroyer<M, true> {
+ void nodes(M& m) const noexcept {
+ m.mNumElements = 0;
+ }
+
+ void nodesDoNotDeallocate(M& m) const noexcept {
+ m.mNumElements = 0;
+ }
+ };
+
+ template <typename M>
+ struct Destroyer<M, false> {
+ void nodes(M& m) const noexcept {
+ m.mNumElements = 0;
+ // clear also resets mInfo to 0, that's sometimes not necessary.
+ 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);
+ n.~Node();
+ }
+ }
+ }
+
+ void nodesDoNotDeallocate(M& m) const noexcept {
+ m.mNumElements = 0;
+ // clear also resets mInfo to 0, that's sometimes not necessary.
+ 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();
+ n.~Node();
+ }
+ }
+ }
+ };
+
+ // Iter ////////////////////////////////////////////////////////////
+
+ struct fast_forward_tag {};
+
+ // generic iterator for both const_iterator and iterator.
+ template <bool IsConst>
+ // NOLINTNEXTLINE(hicpp-special-member-functions,cppcoreguidelines-special-member-functions)
+ class Iter {
+ private:
+ using NodePtr = typename std::conditional<IsConst, Node const*, Node*>::type;
+
+ public:
+ using difference_type = std::ptrdiff_t;
+ using value_type = typename Self::value_type;
+ using reference = typename std::conditional<IsConst, value_type const&, value_type&>::type;
+ using pointer = typename std::conditional<IsConst, value_type const*, value_type*>::type;
+ using iterator_category = std::forward_iterator_tag;
+
+ // default constructed iterator can be compared to itself, but WON'T return true when
+ // 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.
+
+ // Conversion constructor from iterator to const_iterator.
+ template <bool OtherIsConst,
+ typename = typename std::enable_if<IsConst && !OtherIsConst>::type>
+ // NOLINTNEXTLINE(hicpp-explicit-conversions)
+ Iter(Iter<OtherIsConst> const& other) noexcept
+ : mKeyVals(other.mKeyVals)
+ , mInfo(other.mInfo) {}
+
+ Iter(NodePtr valPtr, uint8_t const* infoPtr) noexcept
+ : mKeyVals(valPtr)
+ , mInfo(infoPtr) {}
+
+ Iter(NodePtr valPtr, uint8_t const* infoPtr,
+ fast_forward_tag ROBIN_HOOD_UNUSED(tag) /*unused*/) noexcept
+ : mKeyVals(valPtr)
+ , mInfo(infoPtr) {
+ fastForward();
+ }
+
+ template <bool OtherIsConst,
+ typename = typename std::enable_if<IsConst && !OtherIsConst>::type>
+ Iter& operator=(Iter<OtherIsConst> const& other) noexcept {
+ mKeyVals = other.mKeyVals;
+ mInfo = other.mInfo;
+ return *this;
+ }
+
+ // prefix increment. Undefined behavior if we are at end()!
+ Iter& operator++() noexcept {
+ mInfo++;
+ mKeyVals++;
+ fastForward();
+ return *this;
+ }
+
+ Iter operator++(int) noexcept {
+ Iter tmp = *this;
+ ++(*this);
+ return tmp;
+ }
+
+ reference operator*() const {
+ return **mKeyVals;
+ }
+
+ pointer operator->() const {
+ return &**mKeyVals;
+ }
+
+ template <bool O>
+ bool operator==(Iter<O> const& o) const noexcept {
+ return mKeyVals == o.mKeyVals;
+ }
+
+ template <bool O>
+ bool operator!=(Iter<O> const& o) const noexcept {
+ return mKeyVals != o.mKeyVals;
+ }
+
+ private:
+ // fast forward to the next non-free info byte
+ // I've tried a few variants that don't depend on intrinsics, but unfortunately they are
+ // quite a bit slower than this one. So I've reverted that change again. See map_benchmark.
+ void fastForward() noexcept {
+ size_t n = 0;
+ while (0U == (n = detail::unaligned_load<size_t>(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<uint32_t>(mInfo))) {
+ mInfo += 4;
+ mKeyVals += 4;
+ }
+ if (ROBIN_HOOD_UNLIKELY(0U == detail::unaligned_load<uint16_t>(mInfo))) {
+ mInfo += 2;
+ mKeyVals += 2;
+ }
+ if (ROBIN_HOOD_UNLIKELY(0U == *mInfo)) {
+ mInfo += 1;
+ mKeyVals += 1;
+ }
+#else
+# 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
+ }
+
+ friend class Table<IsFlat, MaxLoadFactor100, key_type, mapped_type, hasher, key_equal>;
+ NodePtr mKeyVals{nullptr};
+ uint8_t const* mInfo{nullptr};
+ };
+
+ ////////////////////////////////////////////////////////////////////
+
+ // highly performance relevant code.
+ // Lower bits are used for indexing into the array (2^n size)
+ // The upper 1-5 bits need to be a reasonable good hash, to save comparisons.
+ template <typename HashKey>
+ void keyToIdx(HashKey&& key, size_t* idx, InfoType* info) const {
+ // 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<uint64_t>(WHash::operator()(key));
+
+ h *= mHashMultiplier;
+ h ^= h >> 33U;
+
+ // the lower InitialInfoNumBits are reserved for info.
+ *info = mInfoInc + static_cast<InfoType>((h & InfoMask) >> mInfoHashShift);
+ *idx = (static_cast<size_t>(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;
+ *info += mInfoInc;
+ }
+
+ void nextWhileLess(InfoType* info, size_t* idx) const noexcept {
+ // unrolling this by hand did not bring any speedups.
+ while (*info < mInfo[*idx]) {
+ next(info, idx);
+ }
+ }
+
+ // Shift everything up by one element. Tries to move stuff around.
+ void
+ shiftUp(size_t startIdx,
+ size_t const insertion_idx) noexcept(std::is_nothrow_move_assignable<Node>::value) {
+ auto idx = startIdx;
+ ::new (static_cast<void*>(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) {
+ ROBIN_HOOD_COUNT(shiftUp)
+ mInfo[idx] = static_cast<uint8_t>(mInfo[idx - 1] + mInfoInc);
+ if (ROBIN_HOOD_UNLIKELY(mInfo[idx] + mInfoInc > 0xFF)) {
+ mMaxNumElementsAllowed = 0;
+ }
+ --idx;
+ }
+ }
+
+ void shiftDown(size_t idx) noexcept(std::is_nothrow_move_assignable<Node>::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.
+ mKeyVals[idx].destroy(*this);
+
+ // until we find one that is either empty or has zero offset.
+ while (mInfo[idx + 1] >= 2 * mInfoInc) {
+ ROBIN_HOOD_COUNT(shiftDown)
+ mInfo[idx] = static_cast<uint8_t>(mInfo[idx + 1] - mInfoInc);
+ mKeyVals[idx] = std::move(mKeyVals[idx + 1]);
+ ++idx;
+ }
+
+ mInfo[idx] = 0;
+ // don't destroy, we've moved it
+ // mKeyVals[idx].destroy(*this);
+ mKeyVals[idx].~Node();
+ }
+
+ // copy of find(), except that it returns iterator instead of const_iterator.
+ template <typename Other>
+ ROBIN_HOOD(NODISCARD)
+ size_t findIdx(Other const& key) const {
+ 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] &&
+ ROBIN_HOOD_LIKELY(WKeyEqual::operator()(key, mKeyVals[idx].getFirst()))) {
+ return idx;
+ }
+ next(&info, &idx);
+ 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
+ : static_cast<size_t>(std::distance(
+ mKeyVals, reinterpret_cast_no_cast_align_warning<Node*>(mInfo)));
+ }
+
+ void cloneData(const Table& o) {
+ Cloner<Table, IsFlat && ROBIN_HOOD_IS_TRIVIALLY_COPYABLE(Node)>()(o, *this);
+ }
+
+ // inserts a keyval that is guaranteed to be new, e.g. when the hashmap is resized.
+ // @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{};
+ 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;
+ info += mInfoInc;
+ }
+
+ // key not found, so we are now exactly where we want to insert it.
+ auto const insertion_idx = idx;
+ auto const insertion_info = static_cast<uint8_t>(info);
+ if (ROBIN_HOOD_UNLIKELY(insertion_info + mInfoInc > 0xFF)) {
+ mMaxNumElementsAllowed = 0;
+ }
+
+ // find an empty spot
+ while (0 != mInfo[idx]) {
+ next(&info, &idx);
+ }
+
+ auto& l = mKeyVals[insertion_idx];
+ if (idx == insertion_idx) {
+ ::new (static_cast<void*>(&l)) Node(std::move(keyval));
+ } else {
+ shiftUp(idx, insertion_idx);
+ l = std::move(keyval);
+ }
+
+ // put at empty spot
+ mInfo[insertion_idx] = insertion_info;
+
+ ++mNumElements;
+ }
+
+public:
+ using iterator = Iter<false>;
+ using const_iterator = Iter<true>;
+
+ 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 <typename Iter>
+ 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);
+ }
+
+ Table(std::initializer_list<value_type> 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());
+ }
+
+ Table(Table&& o) noexcept
+ : WHash(std::move(static_cast<WHash&>(o)))
+ , WKeyEqual(std::move(static_cast<WKeyEqual&>(o)))
+ , DataPool(std::move(static_cast<DataPool&>(o))) {
+ 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);
+ mMask = std::move(o.mMask);
+ mMaxNumElementsAllowed = std::move(o.mMaxNumElementsAllowed);
+ mInfoInc = std::move(o.mInfoInc);
+ mInfoHashShift = std::move(o.mInfoHashShift);
+ // set other's mask to 0 so its destructor won't do anything
+ o.init();
+ }
+ }
+
+ 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);
+ mMask = std::move(o.mMask);
+ mMaxNumElementsAllowed = std::move(o.mMaxNumElementsAllowed);
+ mInfoInc = std::move(o.mInfoInc);
+ mInfoHashShift = std::move(o.mInfoHashShift);
+ WHash::operator=(std::move(static_cast<WHash&>(o)));
+ WKeyEqual::operator=(std::move(static_cast<WKeyEqual&>(o)));
+ DataPool::operator=(std::move(static_cast<DataPool&>(o)));
+
+ o.init();
+
+ } else {
+ // nothing in the other map => just clear us.
+ clear();
+ }
+ }
+ return *this;
+ }
+
+ Table(const Table& o)
+ : WHash(static_cast<const WHash&>(o))
+ , WKeyEqual(static_cast<const WKeyEqual&>(o))
+ , DataPool(static_cast<const DataPool&>(o)) {
+ 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<Node*>(
+ detail::assertNotNull<std::bad_alloc>(std::malloc(numBytesTotal)));
+ // no need for calloc because clonData does memcpy
+ mInfo = reinterpret_cast<uint8_t*>(mKeyVals + numElementsWithBuffer);
+ mNumElements = o.mNumElements;
+ mMask = o.mMask;
+ mMaxNumElementsAllowed = o.mMaxNumElementsAllowed;
+ mInfoInc = o.mInfoInc;
+ mInfoHashShift = o.mInfoHashShift;
+ cloneData(o);
+ }
+ }
+
+ // Creates a copy of the given map. Copy constructor of each entry is used.
+ // 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.
+ if (o.empty()) {
+ if (0 == mMask) {
+ // nothing to do, we are empty too
+ return *this;
+ }
+
+ // not empty: destroy what we have there
+ // clear also resets mInfo to 0, that's sometimes not necessary.
+ destroy();
+ init();
+ WHash::operator=(static_cast<const WHash&>(o));
+ WKeyEqual::operator=(static_cast<const WKeyEqual&>(o));
+ DataPool::operator=(static_cast<DataPool const&>(o));
+
+ return *this;
+ }
+
+ // clean up old stuff
+ Destroyer<Self, IsFlat && std::is_trivially_destructible<Node>::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!
+ 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<Node*>(
+ detail::assertNotNull<std::bad_alloc>(std::malloc(numBytesTotal)));
+
+ // no need for calloc here because cloneData performs a memcpy.
+ mInfo = reinterpret_cast<uint8_t*>(mKeyVals + numElementsWithBuffer);
+ // sentinel is set in cloneData
+ }
+ WHash::operator=(static_cast<const WHash&>(o));
+ WKeyEqual::operator=(static_cast<const WKeyEqual&>(o));
+ DataPool::operator=(static_cast<DataPool const&>(o));
+ mHashMultiplier = o.mHashMultiplier;
+ mNumElements = o.mNumElements;
+ mMask = o.mMask;
+ mMaxNumElementsAllowed = o.mMaxNumElementsAllowed;
+ mInfoInc = o.mInfoInc;
+ mInfoHashShift = o.mInfoHashShift;
+ cloneData(o);
+
+ return *this;
+ }
+
+ // Swaps everything between the two maps.
+ 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)
+ 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.
+ return;
+ }
+
+ Destroyer<Self, IsFlat && std::is_trivially_destructible<Node>::value>{}.nodes(*this);
+
+ auto const numElementsWithBuffer = calcNumElementsWithBuffer(mMask + 1);
+ // clear everything, then set the sentinel again
+ uint8_t const z = 0;
+ std::fill(mInfo, mInfo + calcNumBytesInfo(numElementsWithBuffer), z);
+ mInfo[numElementsWithBuffer] = 1;
+
+ mInfoInc = InitialInfoInc;
+ mInfoHashShift = InitialInfoHashShift;
+ }
+
+ // Destroys the map and all it's contents.
+ ~Table() {
+ ROBIN_HOOD_TRACE(this)
+ destroy();
+ }
+
+ // 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) {
+ if (!has(otherEntry)) {
+ return false;
+ }
+ }
+
+ return true;
+ }
+
+ bool operator!=(const Table& other) const {
+ ROBIN_HOOD_TRACE(this)
+ return !operator==(other);
+ }
+
+ template <typename Q = mapped_type>
+ typename std::enable_if<!std::is_void<Q>::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<void*>(&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();
+ }
+
+ template <typename Q = mapped_type>
+ typename std::enable_if<!std::is_void<Q>::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<void*>(&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 <typename Iter>
+ void insert(Iter first, Iter last) {
+ for (; first != last; ++first) {
+ // value_type ctor needed because this might be called with std::pair's
+ insert(value_type(*first));
+ }
+ }
+
+ void insert(std::initializer_list<value_type> ilist) {
+ for (auto&& vt : ilist) {
+ insert(std::move(vt));
+ }
+ }
+
+ template <typename... Args>
+ std::pair<iterator, bool> emplace(Args&&... args) {
+ ROBIN_HOOD_TRACE(this)
+ Node n{*this, std::forward<Args>(args)...};
+ auto idxAndState = insertKeyPrepareEmptySpot(getFirstConst(n));
+ switch (idxAndState.second) {
+ case InsertionState::key_found:
+ n.destroy(*this);
+ break;
+
+ case InsertionState::new_node:
+ ::new (static_cast<void*>(&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 std::make_pair(iterator(mKeyVals + idxAndState.first, mInfo + idxAndState.first),
+ InsertionState::key_found != idxAndState.second);
+ }
+
+ template <typename... Args>
+ iterator emplace_hint(const_iterator position, Args&&... args) {
+ (void)position;
+ return emplace(std::forward<Args>(args)...).first;
+ }
+
+ template <typename... Args>
+ std::pair<iterator, bool> try_emplace(const key_type& key, Args&&... args) {
+ return try_emplace_impl(key, std::forward<Args>(args)...);
+ }
+
+ template <typename... Args>
+ std::pair<iterator, bool> try_emplace(key_type&& key, Args&&... args) {
+ return try_emplace_impl(std::move(key), std::forward<Args>(args)...);
+ }
+
+ template <typename... Args>
+ iterator try_emplace(const_iterator hint, const key_type& key, Args&&... args) {
+ (void)hint;
+ return try_emplace_impl(key, std::forward<Args>(args)...).first;
+ }
+
+ template <typename... Args>
+ iterator try_emplace(const_iterator hint, key_type&& key, Args&&... args) {
+ (void)hint;
+ return try_emplace_impl(std::move(key), std::forward<Args>(args)...).first;
+ }
+
+ template <typename Mapped>
+ std::pair<iterator, bool> insert_or_assign(const key_type& key, Mapped&& obj) {
+ return insertOrAssignImpl(key, std::forward<Mapped>(obj));
+ }
+
+ template <typename Mapped>
+ std::pair<iterator, bool> insert_or_assign(key_type&& key, Mapped&& obj) {
+ return insertOrAssignImpl(std::move(key), std::forward<Mapped>(obj));
+ }
+
+ template <typename Mapped>
+ iterator insert_or_assign(const_iterator hint, const key_type& key, Mapped&& obj) {
+ (void)hint;
+ return insertOrAssignImpl(key, std::forward<Mapped>(obj)).first;
+ }
+
+ template <typename Mapped>
+ iterator insert_or_assign(const_iterator hint, key_type&& key, Mapped&& obj) {
+ (void)hint;
+ return insertOrAssignImpl(std::move(key), std::forward<Mapped>(obj)).first;
+ }
+
+ std::pair<iterator, bool> insert(const value_type& keyval) {
+ ROBIN_HOOD_TRACE(this)
+ return emplace(keyval);
+ }
+
+ iterator insert(const_iterator hint, const value_type& keyval) {
+ (void)hint;
+ return emplace(keyval).first;
+ }
+
+ std::pair<iterator, bool> 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)
+ auto kv = mKeyVals + findIdx(key);
+ if (kv != reinterpret_cast_no_cast_align_warning<Node*>(mInfo)) {
+ return 1;
+ }
+ return 0;
+ }
+
+ template <typename OtherKey, typename Self_ = Self>
+ // NOLINTNEXTLINE(modernize-use-nodiscard)
+ typename std::enable_if<Self_::is_transparent, size_t>::type count(const OtherKey& key) const {
+ ROBIN_HOOD_TRACE(this)
+ auto kv = mKeyVals + findIdx(key);
+ if (kv != reinterpret_cast_no_cast_align_warning<Node*>(mInfo)) {
+ return 1;
+ }
+ return 0;
+ }
+
+ bool contains(const key_type& key) const { // NOLINT(modernize-use-nodiscard)
+ return 1U == count(key);
+ }
+
+ template <typename OtherKey, typename Self_ = Self>
+ // NOLINTNEXTLINE(modernize-use-nodiscard)
+ typename std::enable_if<Self_::is_transparent, bool>::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
+ template <typename Q = mapped_type>
+ // NOLINTNEXTLINE(modernize-use-nodiscard)
+ typename std::enable_if<!std::is_void<Q>::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<Node*>(mInfo)) {
+ doThrow<std::out_of_range>("key not found");
+ }
+ return kv->getSecond();
+ }
+
+ // Returns a reference to the value found for key.
+ // Throws std::out_of_range if element cannot be found
+ template <typename Q = mapped_type>
+ // NOLINTNEXTLINE(modernize-use-nodiscard)
+ typename std::enable_if<!std::is_void<Q>::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<Node*>(mInfo)) {
+ doThrow<std::out_of_range>("key not found");
+ }
+ return kv->getSecond();
+ }
+
+ const_iterator find(const key_type& key) const { // NOLINT(modernize-use-nodiscard)
+ ROBIN_HOOD_TRACE(this)
+ const size_t idx = findIdx(key);
+ return const_iterator{mKeyVals + idx, mInfo + idx};
+ }
+
+ template <typename OtherKey>
+ const_iterator find(const OtherKey& key, is_transparent_tag /*unused*/) const {
+ ROBIN_HOOD_TRACE(this)
+ const size_t idx = findIdx(key);
+ return const_iterator{mKeyVals + idx, mInfo + idx};
+ }
+
+ template <typename OtherKey, typename Self_ = Self>
+ typename std::enable_if<Self_::is_transparent, // NOLINT(modernize-use-nodiscard)
+ const_iterator>::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)
+ const size_t idx = findIdx(key);
+ return iterator{mKeyVals + idx, mInfo + idx};
+ }
+
+ template <typename OtherKey>
+ iterator find(const OtherKey& key, is_transparent_tag /*unused*/) {
+ ROBIN_HOOD_TRACE(this)
+ const size_t idx = findIdx(key);
+ return iterator{mKeyVals + idx, mInfo + idx};
+ }
+
+ template <typename OtherKey, typename Self_ = Self>
+ typename std::enable_if<Self_::is_transparent, iterator>::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)
+ if (empty()) {
+ return end();
+ }
+ return iterator(mKeyVals, mInfo, fast_forward_tag{});
+ }
+ const_iterator begin() const { // NOLINT(modernize-use-nodiscard)
+ ROBIN_HOOD_TRACE(this)
+ return cbegin();
+ }
+ const_iterator cbegin() const { // NOLINT(modernize-use-nodiscard)
+ ROBIN_HOOD_TRACE(this)
+ if (empty()) {
+ return cend();
+ }
+ return const_iterator(mKeyVals, mInfo, fast_forward_tag{});
+ }
+
+ iterator end() {
+ 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<Node*>(mInfo), nullptr};
+ }
+ const_iterator end() const { // NOLINT(modernize-use-nodiscard)
+ ROBIN_HOOD_TRACE(this)
+ return cend();
+ }
+ const_iterator cend() const { // NOLINT(modernize-use-nodiscard)
+ ROBIN_HOOD_TRACE(this)
+ return const_iterator{reinterpret_cast_no_cast_align_warning<Node*>(mInfo), nullptr};
+ }
+
+ iterator erase(const_iterator pos) {
+ ROBIN_HOOD_TRACE(this)
+ // its safe to perform const cast here
+ // NOLINTNEXTLINE(cppcoreguidelines-pro-type-const-cast)
+ return erase(iterator{const_cast<Node*>(pos.mKeyVals), const_cast<uint8_t*>(pos.mInfo)});
+ }
+
+ // Erases element at pos, returns iterator to the next element.
+ iterator erase(iterator pos) {
+ ROBIN_HOOD_TRACE(this)
+ // we assume that pos always points to a valid entry, and not end().
+ auto const idx = static_cast<size_t>(pos.mKeyVals - mKeyVals);
+
+ shiftDown(idx);
+ --mNumElements;
+
+ if (*pos.mInfo) {
+ // we've backward shifted, return this again
+ return pos;
+ }
+
+ // no backward shift, return next element
+ return ++pos;
+ }
+
+ size_t erase(const key_type& key) {
+ 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] && WKeyEqual::operator()(key, mKeyVals[idx].getFirst())) {
+ shiftDown(idx);
+ --mNumElements;
+ return 1;
+ }
+ next(&info, &idx);
+ } while (info <= mInfo[idx]);
+
+ // nothing found to delete
+ return 0;
+ }
+
+ // 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) {
+ // forces a reserve
+ reserve(c, true);
+ }
+
+ // reserves space for the specified number of elements. Makes sure the old data fits.
+ // Exactly the same as rehash(c). Use rehash(0) to shrink to fit.
+ void reserve(size_t c) {
+ // 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) < mNumElements && 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 (newSize < mMask + 1) {
+ rehashPowerOfTwo(newSize, true);
+ }
+ }
+
+ size_type size() const noexcept { // NOLINT(modernize-use-nodiscard)
+ ROBIN_HOOD_TRACE(this)
+ return mNumElements;
+ }
+
+ size_type max_size() const noexcept { // NOLINT(modernize-use-nodiscard)
+ ROBIN_HOOD_TRACE(this)
+ return static_cast<size_type>(-1);
+ }
+
+ ROBIN_HOOD(NODISCARD) bool empty() const noexcept {
+ ROBIN_HOOD_TRACE(this)
+ return 0 == mNumElements;
+ }
+
+ float max_load_factor() const noexcept { // NOLINT(modernize-use-nodiscard)
+ 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)
+ return static_cast<float>(size()) / static_cast<float>(mMask + 1);
+ }
+
+ ROBIN_HOOD(NODISCARD) size_t mask() const noexcept {
+ ROBIN_HOOD_TRACE(this)
+ return mMask;
+ }
+
+ ROBIN_HOOD(NODISCARD) size_t calcMaxNumElementsAllowed(size_t maxElements) const noexcept {
+ if (ROBIN_HOOD_LIKELY(maxElements <= (std::numeric_limits<size_t>::max)() / 100)) {
+ return maxElements * MaxLoadFactor100 / 100;
+ }
+
+ // we might be a bit inprecise, but since maxElements is quite large that doesn't matter
+ return (maxElements / 100) * MaxLoadFactor100;
+ }
+
+ 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);
+ }
+
+ ROBIN_HOOD(NODISCARD)
+ size_t calcNumElementsWithBuffer(size_t numElements) const noexcept {
+ auto maxNumElementsAllowed = calcMaxNumElementsAllowed(numElements);
+ return numElements + (std::min)(maxNumElementsAllowed, (static_cast<size_t>(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);
+#else
+ // make sure we're doing 64bit operations, so we are at least safe against 32bit overflows.
+ auto const ne = static_cast<uint64_t>(numElements);
+ auto const s = static_cast<uint64_t>(sizeof(Node));
+ auto const infos = static_cast<uint64_t>(calcNumBytesInfo(numElements));
+
+ auto const total64 = ne * s + infos;
+ auto const total = static_cast<size_t>(total64);
+
+ if (ROBIN_HOOD_UNLIKELY(static_cast<uint64_t>(total) != total64)) {
+ throwOverflowError();
+ }
+ return total;
+#endif
+ }
+
+private:
+ template <typename Q = mapped_type>
+ ROBIN_HOOD(NODISCARD)
+ typename std::enable_if<!std::is_void<Q>::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 <typename Q = mapped_type>
+ ROBIN_HOOD(NODISCARD)
+ typename std::enable_if<std::is_void<Q>::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
+ // 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 oldMaxElementsWithBuffer = calcNumElementsWithBuffer(mMask + 1);
+
+ // resize operation: move stuff
+ 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();
+ }
+ }
+
+ // 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<Node*>(&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
+ }
+
+ template <typename OtherKey, typename... Args>
+ std::pair<iterator, bool> try_emplace_impl(OtherKey&& key, Args&&... args) {
+ ROBIN_HOOD_TRACE(this)
+ auto idxAndState = insertKeyPrepareEmptySpot(key);
+ switch (idxAndState.second) {
+ case InsertionState::key_found:
+ break;
+
+ case InsertionState::new_node:
+ ::new (static_cast<void*>(&mKeyVals[idxAndState.first])) Node(
+ *this, std::piecewise_construct, std::forward_as_tuple(std::forward<OtherKey>(key)),
+ std::forward_as_tuple(std::forward<Args>(args)...));
+ break;
+
+ case InsertionState::overwrite_node:
+ mKeyVals[idxAndState.first] = Node(*this, std::piecewise_construct,
+ std::forward_as_tuple(std::forward<OtherKey>(key)),
+ std::forward_as_tuple(std::forward<Args>(args)...));
+ break;
+
+ case InsertionState::overflow_error:
+ throwOverflowError();
+ break;
+ }
+
+ return std::make_pair(iterator(mKeyVals + idxAndState.first, mInfo + idxAndState.first),
+ InsertionState::key_found != idxAndState.second);
+ }
+
+ template <typename OtherKey, typename Mapped>
+ std::pair<iterator, bool> 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<Mapped>(obj);
+ break;
+
+ case InsertionState::new_node:
+ ::new (static_cast<void*>(&mKeyVals[idxAndState.first])) Node(
+ *this, std::piecewise_construct, std::forward_as_tuple(std::forward<OtherKey>(key)),
+ std::forward_as_tuple(std::forward<Mapped>(obj)));
+ break;
+
+ case InsertionState::overwrite_node:
+ mKeyVals[idxAndState.first] = Node(*this, std::piecewise_construct,
+ std::forward_as_tuple(std::forward<OtherKey>(key)),
+ std::forward_as_tuple(std::forward<Mapped>(obj)));
+ break;
+
+ case InsertionState::overflow_error:
+ throwOverflowError();
+ break;
+ }
+
+ return std::make_pair(iterator(mKeyVals + idxAndState.first, mInfo + idxAndState.first),
+ InsertionState::key_found != idxAndState.second);
+ }
+
+ void initData(size_t max_elements) {
+ mNumElements = 0;
+ mMask = max_elements - 1;
+ mMaxNumElementsAllowed = calcMaxNumElementsAllowed(max_elements);
+
+ auto const numElementsWithBuffer = calcNumElementsWithBuffer(max_elements);
+
+ // malloc & zero mInfo. Faster than calloc everything.
+ auto const numBytesTotal = calcNumBytesTotal(numElementsWithBuffer);
+ ROBIN_HOOD_LOG("std::calloc " << numBytesTotal << " = calcNumBytesTotal("
+ << numElementsWithBuffer << ")")
+ mKeyVals = reinterpret_cast<Node*>(
+ detail::assertNotNull<std::bad_alloc>(std::malloc(numBytesTotal)));
+ mInfo = reinterpret_cast<uint8_t*>(mKeyVals + numElementsWithBuffer);
+ std::memset(mInfo, 0, numBytesTotal - numElementsWithBuffer * sizeof(Node));
+
+ // set sentinel
+ mInfo[numElementsWithBuffer] = 1;
+
+ mInfoInc = InitialInfoInc;
+ mInfoHashShift = InitialInfoHashShift;
+ }
+
+ 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 <typename OtherKey>
+ std::pair<size_t, InsertionState> 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 (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(idx, InsertionState::key_found);
+ }
+ next(&info, &idx);
+ }
+
+ // unlikely that this evaluates to true
+ if (ROBIN_HOOD_UNLIKELY(mNumElements >= mMaxNumElementsAllowed)) {
+ if (!increase_size()) {
+ return std::make_pair(size_t(0), InsertionState::overflow_error);
+ }
+ continue;
+ }
+
+ // 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;
+ }
+
+ // find an empty spot
+ while (0 != mInfo[idx]) {
+ next(&info, &idx);
+ }
+
+ if (idx != insertion_idx) {
+ shiftUp(idx, insertion_idx);
+ }
+ // put at empty spot
+ mInfo[insertion_idx] = static_cast<uint8_t>(insertion_info);
+ ++mNumElements;
+ 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))
+ if (mInfoInc <= 2) {
+ // need to be > 2 so that shift works (otherwise undefined behavior!)
+ return false;
+ }
+ // we got space left, try to make info smaller
+ mInfoInc = static_cast<uint8_t>(mInfoInc >> 1U);
+
+ // 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 numElementsWithBuffer = calcNumElementsWithBuffer(mMask + 1);
+
+ for (size_t i = 0; i < numElementsWithBuffer; i += 8) {
+ auto val = unaligned_load<uint64_t>(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;
+ }
+
+ // True if resize was possible, false otherwise
+ bool increase_size() {
+ // nothing allocated yet? just allocate InitialNumElements
+ if (0 == mMask) {
+ initData(InitialNumElements);
+ return true;
+ }
+
+ auto const maxNumElementsAllowed = calcMaxNumElementsAllowed(mMask + 1);
+ if (mNumElements < maxNumElementsAllowed && try_increase_info()) {
+ return true;
+ }
+
+ ROBIN_HOOD_LOG("mNumElements=" << mNumElements << ", maxNumElementsAllowed="
+ << maxNumElementsAllowed << ", load="
+ << (static_cast<double>(mNumElements) * 100.0 /
+ (static_cast<double>(mMask) + 1)))
+
+ if (mNumElements * 2 < calcMaxNumElementsAllowed(mMask + 1)) {
+ // 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;
+ }
+
+ 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() {
+ if (0 == mMask) {
+ // don't deallocate!
+ return;
+ }
+
+ Destroyer<Self, IsFlat && std::is_trivially_destructible<Node>::value>{}
+ .nodesDoNotDeallocate(*this);
+
+ // 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<Node*>(&mMask)) {
+ ROBIN_HOOD_LOG("std::free")
+ std::free(mKeyVals);
+ }
+ }
+
+ void init() noexcept {
+ mKeyVals = reinterpret_cast_no_cast_align_warning<Node*>(&mMask);
+ mInfo = reinterpret_cast<uint8_t*>(&mMask);
+ mNumElements = 0;
+ mMask = 0;
+ mMaxNumElementsAllowed = 0;
+ mInfoInc = InitialInfoInc;
+ mInfoHashShift = InitialInfoHashShift;
+ }
+
+ // members are sorted so no padding occurs
+ uint64_t mHashMultiplier = UINT64_C(0xc4ceb9fe1a85ec53); // 8 byte 8
+ Node* mKeyVals = reinterpret_cast_no_cast_align_warning<Node*>(&mMask); // 8 byte 16
+ uint8_t* mInfo = reinterpret_cast<uint8_t*>(&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 Key, typename T, typename Hash = hash<Key>,
+ typename KeyEqual = std::equal_to<Key>, size_t MaxLoadFactor100 = 80>
+using unordered_flat_map = detail::Table<true, MaxLoadFactor100, Key, T, Hash, KeyEqual>;
+
+template <typename Key, typename T, typename Hash = hash<Key>,
+ typename KeyEqual = std::equal_to<Key>, size_t MaxLoadFactor100 = 80>
+using unordered_node_map = detail::Table<false, MaxLoadFactor100, Key, T, Hash, KeyEqual>;
+
+template <typename Key, typename T, typename Hash = hash<Key>,
+ typename KeyEqual = std::equal_to<Key>, size_t MaxLoadFactor100 = 80>
+using unordered_map =
+ detail::Table<sizeof(robin_hood::pair<Key, T>) <= sizeof(size_t) * 6 &&
+ std::is_nothrow_move_constructible<robin_hood::pair<Key, T>>::value &&
+ std::is_nothrow_move_assignable<robin_hood::pair<Key, T>>::value,
+ MaxLoadFactor100, Key, T, Hash, KeyEqual>;
+
+// set
+
+template <typename Key, typename Hash = hash<Key>, typename KeyEqual = std::equal_to<Key>,
+ size_t MaxLoadFactor100 = 80>
+using unordered_flat_set = detail::Table<true, MaxLoadFactor100, Key, void, Hash, KeyEqual>;
+
+template <typename Key, typename Hash = hash<Key>, typename KeyEqual = std::equal_to<Key>,
+ size_t MaxLoadFactor100 = 80>
+using unordered_node_set = detail::Table<false, MaxLoadFactor100, Key, void, Hash, KeyEqual>;
+
+template <typename Key, typename Hash = hash<Key>, typename KeyEqual = std::equal_to<Key>,
+ size_t MaxLoadFactor100 = 80>
+using unordered_set = detail::Table<sizeof(Key) <= sizeof(size_t) * 6 &&
+ std::is_nothrow_move_constructible<Key>::value &&
+ std::is_nothrow_move_assignable<Key>::value,
+ MaxLoadFactor100, Key, void, Hash, KeyEqual>;
+
+} // namespace robin_hood
+
+#endif
diff --git a/contrib/libs/jinja2cpp/src/serialize_filters.cpp b/contrib/libs/jinja2cpp/src/serialize_filters.cpp
new file mode 100644
index 0000000000..f373492967
--- /dev/null
+++ b/contrib/libs/jinja2cpp/src/serialize_filters.cpp
@@ -0,0 +1,442 @@
+#include "filters.h"
+#include "generic_adapters.h"
+#include "out_stream.h"
+#include "testers.h"
+#include "value_helpers.h"
+#include "value_visitors.h"
+
+#include <fmt/args.h>
+
+#include <algorithm>
+#include <numeric>
+#include <random>
+#include <sstream>
+#include <string>
+
+#ifdef JINJA2CPP_WITH_JSON_BINDINGS_BOOST
+#error #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
+{
+namespace filters
+{
+struct PrettyPrinter : visitors::BaseVisitor<std::string>
+{
+ using BaseVisitor::operator();
+
+ PrettyPrinter(const RenderContext* context)
+ : m_context(context)
+ {
+ }
+
+ std::string operator()(const ListAdapter& list) const
+ {
+ std::string str;
+ auto os = std::back_inserter(str);
+
+ fmt::format_to(os, "[");
+ bool isFirst = true;
+
+ for (auto& v : list)
+ {
+ if (isFirst)
+ isFirst = false;
+ else
+ fmt::format_to(os, ", ");
+ fmt::format_to(os, "{}", Apply<PrettyPrinter>(v, m_context));
+ }
+ fmt::format_to(os, "]");
+
+ return str;
+ }
+
+ std::string operator()(const MapAdapter& map) const
+ {
+ std::string str;
+ auto os = std::back_inserter(str);
+
+ fmt::format_to(os, "{{");
+
+ const auto& keys = map.GetKeys();
+
+ bool isFirst = true;
+ for (auto& k : keys)
+ {
+ if (isFirst)
+ isFirst = false;
+ else
+ fmt::format_to(os, ", ");
+
+ fmt::format_to(os, "'{}': ", k);
+ fmt::format_to(os, "{}", Apply<PrettyPrinter>(map.GetValueByName(k), m_context));
+ }
+
+ fmt::format_to(os, "}}");
+
+ return str;
+ }
+
+ std::string operator()(const KeyValuePair& kwPair) const
+ {
+ std::string str;
+ auto os = std::back_inserter(str);
+
+ fmt::format_to(os, "'{}': ", kwPair.key);
+ fmt::format_to(os, "{}", Apply<PrettyPrinter>(kwPair.value, m_context));
+
+ return str;
+ }
+
+ std::string operator()(const std::string& str) const { return fmt::format("'{}'", str); }
+
+ std::string operator()(const std::string_view& str) const { return fmt::format("'{}'", fmt::basic_string_view<char>(str.data(), str.size())); }
+
+ std::string operator()(const std::wstring& str) const { return fmt::format("'{}'", ConvertString<std::string>(str)); }
+
+ std::string operator()(const std::wstring_view& str) const { return fmt::format("'{}'", ConvertString<std::string>(str)); }
+
+ std::string operator()(bool val) const { return val ? "true"s : "false"s; }
+
+ std::string operator()(EmptyValue) const { return "none"s; }
+
+ std::string operator()(const Callable&) const { return "<callable>"s; }
+
+ std::string operator()(double val) const
+ {
+ std::string str;
+ auto os = std::back_inserter(str);
+
+ fmt::format_to(os, "{:.8g}", val);
+
+ return str;
+ }
+
+ std::string operator()(int64_t val) const { return fmt::format("{}", val); }
+
+ const RenderContext* m_context;
+};
+
+PrettyPrint::PrettyPrint(FilterParams params) {}
+
+InternalValue PrettyPrint::Filter(const InternalValue& baseVal, RenderContext& context)
+{
+ return Apply<PrettyPrinter>(baseVal, &context);
+}
+
+Serialize::Serialize(const FilterParams params, const Serialize::Mode mode)
+ : m_mode(mode)
+{
+ switch (mode)
+ {
+ case JsonMode:
+ ParseParams({ { "indent", false, static_cast<int64_t>(0) } }, params);
+ break;
+ default:
+ break;
+ }
+}
+
+InternalValue Serialize::Filter(const InternalValue& value, RenderContext& context)
+{
+ if (m_mode == JsonMode)
+ {
+ const auto indent = ConvertToInt(this->GetArgumentValue("indent", context));
+ DocumentWrapper jsonDoc;
+ const auto jsonValue = jsonDoc.CreateValue(value);
+ const auto jsonString = jsonValue.AsString(static_cast<uint8_t>(indent));
+ std::string result = ""s;
+ for (char c : jsonString) {
+ if (c == '<') {
+ result.append("\\u003c");
+ } else if (c == '>') {
+ result.append("\\u003e");
+ } else if (c == '&') {
+ result.append("\\u0026");
+ } else if (c == '\'') {
+ result.append("\\u0027");
+ } else {
+ result.push_back(c);
+ }
+ }
+
+ return result;
+ }
+
+ return InternalValue();
+}
+
+namespace
+{
+
+using FormatContext = fmt::format_context;
+using FormatArgument = fmt::basic_format_arg<FormatContext>;
+using FormatDynamicArgsStore = fmt::dynamic_format_arg_store<FormatContext>;
+
+struct FormatArgumentConverter : visitors::BaseVisitor<FormatArgument>
+{
+ using result_t = FormatArgument;
+
+ using BaseVisitor::operator();
+
+ FormatArgumentConverter(const RenderContext* context, FormatDynamicArgsStore& store)
+ : m_context(context)
+ , m_store(store)
+ {
+ }
+
+ FormatArgumentConverter(const RenderContext* context, FormatDynamicArgsStore& store, const std::string& name)
+ : m_context(context)
+ , m_store(store)
+ , m_name(name)
+ , m_named(true)
+ {
+ }
+
+ result_t operator()(const ListAdapter& list) const { return make_result(Apply<PrettyPrinter>(list, m_context)); }
+
+ result_t operator()(const MapAdapter& map) const { return make_result(Apply<PrettyPrinter>(map, m_context)); }
+
+ result_t operator()(const std::string& str) const { return make_result(str); }
+
+ result_t operator()(const std::string_view& str) const { return make_result(std::string(str.data(), str.size())); }
+
+ result_t operator()(const std::wstring& str) const { return make_result(ConvertString<std::string>(str)); }
+
+ result_t operator()(const std::wstring_view& str) const { return make_result(ConvertString<std::string>(str)); }
+
+ result_t operator()(double val) const { return make_result(val); }
+
+ result_t operator()(int64_t val) const { return make_result(val); }
+
+ result_t operator()(bool val) const { return make_result(val ? "true"s : "false"s); }
+
+ result_t operator()(EmptyValue) const { return make_result("none"s); }
+
+ result_t operator()(const Callable&) const { return make_result("<callable>"s); }
+
+ template<typename T>
+ result_t make_result(const T& t) const
+ {
+ if (!m_named)
+ {
+ m_store.push_back(t);
+ }
+ else
+ {
+ m_store.push_back(fmt::arg(m_name.c_str(), t));
+ }
+ return fmt::detail::make_arg<FormatContext>(t);
+ }
+
+ const RenderContext* m_context;
+ FormatDynamicArgsStore& m_store;
+ const std::string m_name;
+ bool m_named = false;
+};
+
+} // namespace
+
+InternalValue StringFormat::Filter(const InternalValue& baseVal, RenderContext& context)
+{
+ // Format library internally likes using non-owning views to complex arguments.
+ // In order to ensure proper lifetime of values and named args,
+ // helper buffer is created and passed to visitors.
+ FormatDynamicArgsStore store;
+ for (auto& arg : m_params.posParams)
+ {
+ Apply<FormatArgumentConverter>(arg->Evaluate(context), &context, store);
+ }
+
+ for (auto& arg : m_params.kwParams)
+ {
+ Apply<FormatArgumentConverter>(arg.second->Evaluate(context), &context, store, arg.first);
+ }
+
+ return InternalValue(fmt::vformat(AsString(baseVal), store));
+}
+
+class XmlAttrPrinter : public visitors::BaseVisitor<std::string>
+{
+public:
+ using BaseVisitor::operator();
+
+ explicit XmlAttrPrinter(RenderContext* context, bool isFirstLevel = false)
+ : m_context(context)
+ , m_isFirstLevel(isFirstLevel)
+ {
+ }
+
+ std::string operator()(const ListAdapter& list) const
+ {
+ EnforceThatNested();
+
+ return EscapeHtml(Apply<PrettyPrinter>(list, m_context));
+ }
+
+ std::string operator()(const MapAdapter& map) const
+ {
+ if (!m_isFirstLevel)
+ {
+ return EscapeHtml(Apply<PrettyPrinter>(map, m_context));
+ }
+
+ std::string str;
+ auto os = std::back_inserter(str);
+
+ const auto& keys = map.GetKeys();
+
+ bool isFirst = true;
+ for (auto& k : keys)
+ {
+ const auto& v = map.GetValueByName(k);
+ const auto item = Apply<XmlAttrPrinter>(v, m_context, false);
+ if (item.length() > 0)
+ {
+ if (isFirst)
+ isFirst = false;
+ else
+ fmt::format_to(os, " ");
+
+ fmt::format_to(os, "{}=\"{}\"", k, item);
+ }
+ }
+
+ return str;
+ }
+
+ std::string operator()(const KeyValuePair& kwPair) const
+ {
+ EnforceThatNested();
+
+ return EscapeHtml(Apply<PrettyPrinter>(kwPair, m_context));
+ }
+
+ std::string operator()(const std::string& str) const
+ {
+ EnforceThatNested();
+
+ return EscapeHtml(str);
+ }
+
+ std::string operator()(const std::string_view& str) const
+ {
+ EnforceThatNested();
+
+ const auto result = fmt::format("{}", fmt::basic_string_view<char>(str.data(), str.size()));
+ return EscapeHtml(result);
+ }
+
+ std::string operator()(const std::wstring& str) const
+ {
+ EnforceThatNested();
+
+ return EscapeHtml(ConvertString<std::string>(str));
+ }
+
+ std::string operator()(const std::wstring_view& str) const
+ {
+ EnforceThatNested();
+
+ const auto result = fmt::format("{}", ConvertString<std::string>(str));
+ return EscapeHtml(result);
+ }
+
+ std::string operator()(bool val) const
+ {
+ EnforceThatNested();
+
+ return val ? "true"s : "false"s;
+ }
+
+ std::string operator()(EmptyValue) const
+ {
+ EnforceThatNested();
+
+ return ""s;
+ }
+
+ std::string operator()(const Callable&) const
+ {
+ EnforceThatNested();
+
+ return ""s;
+ }
+
+ std::string operator()(double val) const
+ {
+ EnforceThatNested();
+
+ std::string str;
+ auto os = std::back_inserter(str);
+
+ fmt::format_to(os, "{:.8g}", val);
+
+ return str;
+ }
+
+ std::string operator()(int64_t val) const
+ {
+ EnforceThatNested();
+
+ return fmt::format("{}", val);
+ }
+
+private:
+ void EnforceThatNested() const
+ {
+ if (m_isFirstLevel)
+ m_context->GetRendererCallback()->ThrowRuntimeError(ErrorCode::InvalidValueType, ValuesList{});
+ }
+
+ std::string EscapeHtml(const std::string &str) const
+ {
+ const auto result = std::accumulate(str.begin(), str.end(), ""s, [](const auto &str, const auto &c)
+ {
+ switch (c)
+ {
+ case '<':
+ return str + "&lt;";
+ break;
+ case '>':
+ return str +"&gt;";
+ break;
+ case '&':
+ return str +"&amp;";
+ break;
+ case '\'':
+ return str +"&#39;";
+ break;
+ case '\"':
+ return str +"&#34;";
+ break;
+ default:
+ return str + c;
+ break;
+ }
+ });
+
+ return result;
+ }
+
+private:
+ RenderContext* m_context;
+ bool m_isFirstLevel;
+};
+
+XmlAttrFilter::XmlAttrFilter(FilterParams) {}
+
+InternalValue XmlAttrFilter::Filter(const InternalValue& baseVal, RenderContext& context)
+{
+ return Apply<XmlAttrPrinter>(baseVal, &context, true);
+}
+
+} // namespace filters
+} // namespace jinja2
diff --git a/contrib/libs/jinja2cpp/src/statements.cpp b/contrib/libs/jinja2cpp/src/statements.cpp
new file mode 100644
index 0000000000..bb4718d2e4
--- /dev/null
+++ b/contrib/libs/jinja2cpp/src/statements.cpp
@@ -0,0 +1,839 @@
+#include "statements.h"
+
+#include "expression_evaluator.h"
+#include "template_impl.h"
+#include "value_visitors.h"
+
+#include <boost/core/null_deleter.hpp>
+
+#include <string>
+
+using namespace std::string_literals;
+
+namespace jinja2
+{
+
+void ForStatement::Render(OutStream& os, RenderContext& values)
+{
+ InternalValue loopVal = m_value->Evaluate(values);
+
+ RenderLoop(loopVal, os, values, 0);
+}
+
+void ForStatement::RenderLoop(const InternalValue& loopVal, OutStream& os, RenderContext& values, int level)
+{
+ auto& context = values.EnterScope();
+
+ InternalValueMap loopVar;
+ context["loop"s] = CreateMapAdapter(&loopVar);
+ if (m_isRecursive)
+ {
+ loopVar["operator()"s] = Callable(Callable::GlobalFunc, [this, level](const CallParams& params, OutStream& stream, RenderContext& context) {
+ bool isSucceeded = false;
+ auto parsedParams = helpers::ParseCallParams({ { "var", true } }, params, isSucceeded);
+ if (!isSucceeded)
+ return;
+
+ auto var = parsedParams["var"];
+ if (var.IsEmpty())
+ return;
+
+ RenderLoop(var, stream, context, level + 1);
+ });
+ loopVar["depth"s] = static_cast<int64_t>(level + 1);
+ loopVar["depth0"s] = static_cast<int64_t>(level);
+ }
+
+ bool isConverted = false;
+ auto loopItems = ConvertToList(loopVal, isConverted, false);
+ ListAdapter filteredList;
+ ListAdapter indexedList;
+ ListAccessorEnumeratorPtr enumerator;
+ size_t itemIdx = 0;
+ if (!isConverted)
+ {
+ if (m_elseBody)
+ m_elseBody->Render(os, values);
+ values.ExitScope();
+ return;
+ }
+
+ std::optional<size_t> listSize;
+ if (m_ifExpr)
+ {
+ filteredList = CreateFilteredAdapter(loopItems, values);
+ enumerator = filteredList.GetEnumerator();
+ }
+ else
+ {
+ enumerator = loopItems.GetEnumerator();
+ listSize = loopItems.GetSize();
+ }
+
+ bool isLast = false;
+ auto makeIndexedList = [&enumerator, &listSize, &indexedList, &itemIdx, &isLast] {
+ if (isLast)
+ listSize = itemIdx;
+
+ InternalValueList items;
+ do
+ {
+ items.push_back(enumerator->GetCurrent());
+ } while (enumerator->MoveNext());
+
+ listSize = itemIdx + items.size() + 1;
+ indexedList = ListAdapter::CreateAdapter(std::move(items));
+ enumerator = indexedList.GetEnumerator();
+ isLast = !enumerator->MoveNext();
+ };
+
+ if (listSize)
+ {
+ int64_t itemsNum = static_cast<int64_t>(listSize.value());
+ loopVar["length"s] = InternalValue(itemsNum);
+ }
+ else
+ {
+ loopVar["length"s] = MakeDynamicProperty([&listSize, &makeIndexedList](const CallParams& /*params*/, RenderContext & /*context*/) -> InternalValue {
+ if (!listSize)
+ makeIndexedList();
+ return static_cast<int64_t>(listSize.value());
+ });
+ }
+ bool loopRendered = false;
+ isLast = !enumerator->MoveNext();
+ InternalValue prevValue;
+ InternalValue curValue;
+ InternalValue nextValue;
+ loopVar["cycle"s] = static_cast<int64_t>(LoopCycleFn);
+ for (; !isLast; ++itemIdx)
+ {
+ prevValue = std::move(curValue);
+ if (itemIdx != 0)
+ {
+ std::swap(curValue, nextValue);
+ loopVar["previtem"s] = prevValue;
+ }
+ else
+ curValue = enumerator->GetCurrent();
+
+ isLast = !enumerator->MoveNext();
+ if (!isLast)
+ {
+ nextValue = enumerator->GetCurrent();
+ loopVar["nextitem"s] = nextValue;
+ }
+ else
+ loopVar.erase("nextitem"s);
+
+ loopRendered = true;
+ loopVar["index"s] = static_cast<int64_t>(itemIdx + 1);
+ loopVar["index0"s] = static_cast<int64_t>(itemIdx);
+ loopVar["first"s] = itemIdx == 0;
+ loopVar["last"s] = isLast;
+
+ if (m_vars.size() > 1)
+ {
+ const auto& valList = ConvertToList(curValue, isConverted);
+ if (!isConverted)
+ continue;
+
+ auto b = valList.begin();
+ auto e = valList.end();
+
+ for (auto& varName : m_vars)
+ {
+ if (b == e)
+ continue;
+ context[varName] = *b;
+ ++ b;
+ }
+ }
+ else
+ context[m_vars[0]] = curValue;
+
+ values.EnterScope();
+ m_mainBody->Render(os, values);
+ values.ExitScope();
+ }
+
+ if (!loopRendered && m_elseBody)
+ m_elseBody->Render(os, values);
+
+ values.ExitScope();
+}
+
+ListAdapter ForStatement::CreateFilteredAdapter(const ListAdapter& loopItems, RenderContext& values) const
+{
+ return ListAdapter::CreateAdapter([e = loopItems.GetEnumerator(), this, &values]() mutable {
+ using ResultType = std::optional<InternalValue>;
+
+ auto& tempContext = values.EnterScope();
+ for (bool finish = !e->MoveNext(); !finish; finish = !e->MoveNext())
+ {
+ auto curValue = e->GetCurrent();
+ if (m_vars.size() > 1)
+ {
+ for (auto& varName : m_vars)
+ tempContext[varName] = Subscript(curValue, varName, &values);
+ }
+ else
+ {
+ tempContext[m_vars[0]] = curValue;
+ }
+
+ if (ConvertToBool(m_ifExpr->Evaluate(values)))
+ {
+ values.ExitScope();
+ return ResultType(std::move(curValue));
+ }
+ }
+ values.ExitScope();
+
+ return ResultType();
+ });
+}
+
+void IfStatement::Render(OutStream& os, RenderContext& values)
+{
+ InternalValue val = m_expr->Evaluate(values);
+ bool isTrue = Apply<visitors::BooleanEvaluator>(val);
+
+ if (isTrue)
+ {
+ m_mainBody->Render(os, values);
+ return;
+ }
+
+ for (auto& b : m_elseBranches)
+ {
+ if (b->ShouldRender(values))
+ {
+ b->Render(os, values);
+ break;
+ }
+ }
+}
+
+bool ElseBranchStatement::ShouldRender(RenderContext& values) const
+{
+ if (!m_expr)
+ return true;
+
+ return Apply<visitors::BooleanEvaluator>(m_expr->Evaluate(values));
+}
+
+void ElseBranchStatement::Render(OutStream& os, RenderContext& values)
+{
+ m_mainBody->Render(os, values);
+}
+
+void SetStatement::AssignBody(InternalValue body, RenderContext& values)
+{
+ auto& scope = values.GetCurrentScope();
+ if (m_fields.size() == 1)
+ scope[m_fields.front()] = std::move(body);
+ else
+ {
+ for (const auto& name : m_fields)
+ scope[name] = Subscript(body, name, &values);
+ }
+}
+
+void SetLineStatement::Render(OutStream&, RenderContext& values)
+{
+ if (!m_expr)
+ return;
+ AssignBody(m_expr->Evaluate(values), values);
+}
+
+InternalValue SetBlockStatement::RenderBody(RenderContext& values)
+{
+ TargetString result;
+ auto stream = values.GetRendererCallback()->GetStreamOnString(result);
+ auto innerValues = values.Clone(true);
+ m_body->Render(stream, innerValues);
+ return result;
+}
+
+void SetRawBlockStatement::Render(OutStream&, RenderContext& values)
+{
+ AssignBody(RenderBody(values), values);
+}
+
+void SetFilteredBlockStatement::Render(OutStream&, RenderContext& values)
+{
+ if (!m_expr)
+ return;
+ AssignBody(m_expr->Evaluate(RenderBody(values), values), values);
+}
+
+class IBlocksRenderer : public IRendererBase
+{
+public:
+ virtual bool HasBlock(const std::string& blockName) = 0;
+ virtual void RenderBlock(const std::string& blockName, OutStream& os, RenderContext& values) = 0;
+};
+
+void ParentBlockStatement::Render(OutStream& os, RenderContext& values)
+{
+ RenderContext innerContext = values.Clone(m_isScoped);
+ bool found = false;
+ auto parentTplVal = values.FindValue("$$__parent_template", found);
+ if (!found)
+ {
+ m_mainBody->Render(os, values);
+ return;
+ }
+
+ bool isConverted = false;
+ auto parentTplsList = ConvertToList(parentTplVal->second, isConverted);
+ if (!isConverted)
+ return;
+
+ IBlocksRenderer* blockRenderer = nullptr; // static_cast<BlocksRenderer*>(*parentTplPtr);
+ for (auto& tplVal : parentTplsList)
+ {
+ auto ptr = GetIf<RendererPtr>(&tplVal);
+ if (!ptr)
+ continue;
+
+ auto parentTplPtr = static_cast<IBlocksRenderer*>(ptr->get());
+
+ if (parentTplPtr->HasBlock(m_name))
+ {
+ blockRenderer = parentTplPtr;
+ break;
+ }
+ }
+
+ if (!blockRenderer)
+ {
+ m_mainBody->Render(os, values);
+ return;
+ }
+
+ auto& scope = innerContext.EnterScope();
+ scope["$$__super_block"] = RendererPtr(this, boost::null_deleter());
+ scope["super"] =
+ Callable(Callable::SpecialFunc, [this](const CallParams&, OutStream& stream, RenderContext& context) { m_mainBody->Render(stream, context); });
+ if (!m_isScoped)
+ scope["$$__parent_template"] = parentTplsList;
+
+ blockRenderer->RenderBlock(m_name, os, innerContext);
+ innerContext.ExitScope();
+
+ auto& globalScope = values.GetGlobalScope();
+ auto selfMap = GetIf<MapAdapter>(&globalScope[std::string("self")]);
+ if (!selfMap->HasValue(m_name))
+ selfMap->SetValue(m_name, MakeWrapped(Callable(Callable::SpecialFunc, [this](const CallParams&, OutStream& stream, RenderContext& context) {
+ Render(stream, context);
+ })));
+}
+
+void BlockStatement::Render(OutStream& os, RenderContext& values)
+{
+ m_mainBody->Render(os, values);
+}
+
+template<typename CharT>
+class ParentTemplateRenderer : public IBlocksRenderer
+{
+public:
+ ParentTemplateRenderer(std::shared_ptr<TemplateImpl<CharT>> tpl, ExtendsStatement::BlocksCollection* blocks)
+ : m_template(tpl)
+ , m_blocks(blocks)
+ {
+ }
+
+ void Render(OutStream& os, RenderContext& values) override
+ {
+ auto& scope = values.GetCurrentScope();
+ InternalValueList parentTemplates;
+ parentTemplates.push_back(InternalValue(RendererPtr(this, boost::null_deleter())));
+ bool isFound = false;
+ auto p = values.FindValue("$$__parent_template", isFound);
+ if (isFound)
+ {
+ bool isConverted = false;
+ auto prevTplsList = ConvertToList(p->second, isConverted);
+ if (isConverted)
+ {
+ for (auto& tpl : prevTplsList)
+ parentTemplates.push_back(tpl);
+ }
+ }
+ scope["$$__parent_template"] = ListAdapter::CreateAdapter(std::move(parentTemplates));
+ m_template->GetRenderer()->Render(os, values);
+ }
+
+ void RenderBlock(const std::string& blockName, OutStream& os, RenderContext& values) override
+ {
+ auto p = m_blocks->find(blockName);
+ if (p == m_blocks->end())
+ return;
+
+ p->second->Render(os, values);
+ }
+
+ bool HasBlock(const std::string& blockName) override { return m_blocks->count(blockName) != 0; }
+
+ bool IsEqual(const IComparable& other) const override
+ {
+ auto* val = dynamic_cast<const ParentTemplateRenderer*>(&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<TemplateImpl<CharT>> m_template;
+ ExtendsStatement::BlocksCollection* m_blocks;
+};
+
+template<typename Result, typename Fn>
+struct TemplateImplVisitor
+{
+ // ExtendsStatement::BlocksCollection* m_blocks;
+ const Fn& m_fn;
+ bool m_throwError{};
+
+ explicit TemplateImplVisitor(const Fn& fn, bool throwError)
+ : m_fn(fn)
+ , m_throwError(throwError)
+ {
+ }
+
+ template<typename CharT>
+ Result operator()(nonstd::expected<std::shared_ptr<TemplateImpl<CharT>>, ErrorInfoTpl<CharT>> tpl) const
+ {
+ if (!m_throwError && !tpl)
+ {
+ return Result{};
+ }
+ else if (!tpl)
+ {
+ throw tpl.error();
+ }
+ return m_fn(tpl.value());
+ }
+
+ Result operator()(EmptyValue) const { return Result(); }
+};
+
+template<typename Result, typename Fn, typename Arg>
+Result VisitTemplateImpl(Arg&& tpl, bool throwError, Fn&& fn)
+{
+ return visit(TemplateImplVisitor<Result, Fn>(fn, throwError), tpl);
+}
+
+template<template<typename T> class RendererTpl, typename CharT, typename... Args>
+auto CreateTemplateRenderer(std::shared_ptr<TemplateImpl<CharT>> tpl, Args&&... args)
+{
+ return std::make_shared<RendererTpl<CharT>>(tpl, std::forward<Args>(args)...);
+}
+
+void ExtendsStatement::Render(OutStream& os, RenderContext& values)
+{
+ if (!m_isPath)
+ {
+ // FIXME: Implement processing of templates
+ return;
+ }
+ auto tpl = values.GetRendererCallback()->LoadTemplate(m_templateName);
+ auto renderer =
+ VisitTemplateImpl<RendererPtr>(tpl, true, [this](auto tplPtr) { return CreateTemplateRenderer<ParentTemplateRenderer>(tplPtr, &m_blocks); });
+ if (renderer)
+ renderer->Render(os, values);
+}
+
+template<typename CharT>
+class IncludedTemplateRenderer : public IRendererBase
+{
+public:
+ IncludedTemplateRenderer(std::shared_ptr<TemplateImpl<CharT>> tpl, bool withContext)
+ : m_template(tpl)
+ , m_withContext(withContext)
+ {
+ }
+
+ void Render(OutStream& os, RenderContext& values) override
+ {
+ RenderContext innerContext = values.Clone(m_withContext);
+ if (m_withContext)
+ innerContext.EnterScope();
+
+ m_template->GetRenderer()->Render(os, innerContext);
+ if (m_withContext)
+ {
+ auto& innerScope = innerContext.GetCurrentScope();
+ auto& scope = values.GetCurrentScope();
+ for (auto& v : innerScope)
+ {
+ scope[v.first] = std::move(v.second);
+ }
+ }
+ }
+
+ bool IsEqual(const IComparable& other) const override
+ {
+ auto* val = dynamic_cast<const IncludedTemplateRenderer<CharT>*>(&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<TemplateImpl<CharT>> m_template;
+ bool m_withContext{};
+};
+
+void IncludeStatement::Render(OutStream& os, RenderContext& values)
+{
+ auto templateNames = m_expr->Evaluate(values);
+ bool isConverted = false;
+ ListAdapter list = ConvertToList(templateNames, isConverted);
+
+ auto doRender = [this, &values, &os](auto&& name) -> bool {
+ auto tpl = values.GetRendererCallback()->LoadTemplate(name);
+
+ try
+ {
+ auto renderer = VisitTemplateImpl<RendererPtr>(
+ tpl, true, [this](auto tplPtr) { return CreateTemplateRenderer<IncludedTemplateRenderer>(tplPtr, m_withContext); });
+
+ if (renderer)
+ {
+ renderer->Render(os, values);
+ return true;
+ }
+ }
+ catch (const ErrorInfoTpl<char>& err)
+ {
+ if (err.GetCode() != ErrorCode::FileNotFound)
+ throw;
+ }
+ catch (const ErrorInfoTpl<wchar_t>& err)
+ {
+ if (err.GetCode() != ErrorCode::FileNotFound)
+ throw;
+ }
+
+ return false;
+ };
+
+ bool rendered = false;
+ if (isConverted)
+ {
+ for (auto& name : list)
+ {
+ rendered = doRender(name);
+ if (rendered)
+ break;
+ }
+ }
+ else
+ {
+ rendered = doRender(templateNames);
+ }
+
+ if (!rendered && !m_ignoreMissing)
+ {
+ InternalValueList files;
+ ValuesList extraParams;
+ if (isConverted)
+ {
+ extraParams.push_back(IntValue2Value(templateNames));
+ }
+ else
+ {
+ files.push_back(templateNames);
+ extraParams.push_back(IntValue2Value(ListAdapter::CreateAdapter(std::move(files))));
+ }
+
+ values.GetRendererCallback()->ThrowRuntimeError(ErrorCode::TemplateNotFound, std::move(extraParams));
+ }
+}
+
+class ImportedMacroRenderer : public IRendererBase
+{
+public:
+ explicit ImportedMacroRenderer(InternalValueMap&& map, bool withContext)
+ : m_importedContext(std::move(map))
+ , m_withContext(withContext)
+ {
+ }
+
+ void Render(OutStream& /*os*/, RenderContext& /*values*/) override {}
+
+ void InvokeMacro(const Callable& callable, const CallParams& params, OutStream& stream, RenderContext& context)
+ {
+ auto ctx = context.Clone(m_withContext);
+ ctx.BindScope(&m_importedContext);
+ callable.GetStatementCallable()(params, stream, ctx);
+ }
+
+ static void InvokeMacro(const std::string& contextName, const Callable& callable, const CallParams& params, OutStream& stream, RenderContext& context)
+ {
+ bool contextValFound = false;
+ auto contextVal = context.FindValue(contextName, contextValFound);
+ if (!contextValFound)
+ return;
+
+ auto rendererPtr = GetIf<RendererPtr>(&contextVal->second);
+ if (!rendererPtr)
+ return;
+
+ auto renderer = static_cast<ImportedMacroRenderer*>(rendererPtr->get());
+ renderer->InvokeMacro(callable, params, stream, context);
+ }
+
+ bool IsEqual(const IComparable& other) const override
+ {
+ auto* val = dynamic_cast<const ImportedMacroRenderer*>(&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{};
+};
+
+void ImportStatement::Render(OutStream& /*os*/, RenderContext& values)
+{
+ auto name = m_nameExpr->Evaluate(values);
+
+ if (!m_renderer)
+ {
+ auto tpl = values.GetRendererCallback()->LoadTemplate(name);
+ m_renderer = VisitTemplateImpl<RendererPtr>(tpl, true, [](auto tplPtr) { return CreateTemplateRenderer<IncludedTemplateRenderer>(tplPtr, true); });
+ }
+
+ if (!m_renderer)
+ return;
+
+ std::string scopeName;
+ {
+ TargetString tsScopeName = values.GetRendererCallback()->GetAsTargetString(name);
+ scopeName = "$$_imported_" + GetAsSameString(scopeName, tsScopeName).value();
+ }
+
+ TargetString str;
+ auto tmpStream = values.GetRendererCallback()->GetStreamOnString(str);
+
+ RenderContext newContext = values.Clone(m_withContext);
+ InternalValueMap importedScope;
+ {
+ auto& intImportedScope = newContext.EnterScope();
+ m_renderer->Render(tmpStream, newContext);
+ importedScope = std::move(intImportedScope);
+ }
+
+ ImportNames(values, importedScope, scopeName);
+ values.GetCurrentScope()[scopeName] =
+ std::static_pointer_cast<IRendererBase>(std::make_shared<ImportedMacroRenderer>(std::move(importedScope), m_withContext));
+}
+
+void ImportStatement::ImportNames(RenderContext& values, InternalValueMap& importedScope, const std::string& scopeName) const
+{
+ InternalValueMap importedNs;
+
+ for (auto& var : importedScope)
+ {
+ if (var.first.empty())
+ continue;
+
+ if (var.first[0] == '_')
+ continue;
+
+ auto mappedP = m_namesToImport.find(var.first);
+ if (!m_namespace && mappedP == m_namesToImport.end())
+ continue;
+
+ InternalValue imported;
+ auto callable = GetIf<Callable>(&var.second);
+ if (!callable)
+ {
+ imported = std::move(var.second);
+ }
+ else if (callable->GetKind() == Callable::Macro)
+ {
+ imported = Callable(Callable::Macro, [fn = std::move(*callable), scopeName](const CallParams& params, OutStream& stream, RenderContext& context) {
+ ImportedMacroRenderer::InvokeMacro(scopeName, fn, params, stream, context);
+ });
+ }
+ else
+ {
+ continue;
+ }
+
+ if (m_namespace)
+ importedNs[var.first] = std::move(imported);
+ else
+ values.GetCurrentScope()[mappedP->second] = std::move(imported);
+ }
+
+ if (m_namespace)
+ values.GetCurrentScope()[m_namespace.value()] = CreateMapAdapter(std::move(importedNs));
+}
+
+std::vector<ArgumentInfo> MacroStatement::PrepareMacroParams(RenderContext& values)
+{
+ std::vector<ArgumentInfo> preparedParams;
+
+ for (auto& p : m_params)
+ {
+ ArgumentInfo info(p.paramName, !p.defaultValue);
+ if (p.defaultValue)
+ info.defaultVal = p.defaultValue->Evaluate(values);
+ preparedParams.push_back(std::move(info));
+ }
+
+ return preparedParams;
+}
+
+void MacroStatement::Render(OutStream&, RenderContext& values)
+{
+ auto p = PrepareMacroParams(values);
+
+ values.GetCurrentScope()[m_name] = Callable(Callable::Macro, [this, params = std::move(p)](const CallParams& callParams, OutStream& stream, RenderContext& context) {
+ InvokeMacroRenderer(params, callParams, stream, context);
+ });
+}
+
+void MacroStatement::InvokeMacroRenderer(const std::vector<ArgumentInfo>& params, const CallParams& callParams, OutStream& stream, RenderContext& context)
+{
+ InternalValueMap callArgs;
+ InternalValueMap kwArgs;
+ InternalValueList varArgs;
+
+ SetupCallArgs(params, callParams, context, callArgs, kwArgs, varArgs);
+ InternalValueList arguments;
+ InternalValueList defaults;
+ for (auto& a : params)
+ {
+ arguments.emplace_back(a.name);
+ defaults.emplace_back(a.defaultVal);
+ }
+
+ auto& scope = context.EnterScope();
+ for (auto& a : callArgs)
+ scope[a.first] = std::move(a.second);
+
+ scope["kwargs"s] = CreateMapAdapter(std::move(kwArgs));
+ scope["varargs"s] = ListAdapter::CreateAdapter(std::move(varArgs));
+
+ scope["name"s] = static_cast<std::string>(m_name);
+ scope["arguments"s] = ListAdapter::CreateAdapter(std::move(arguments));
+ scope["defaults"s] = ListAdapter::CreateAdapter(std::move(defaults));
+
+ m_mainBody->Render(stream, context);
+
+ context.ExitScope();
+}
+
+void MacroStatement::SetupCallArgs(const std::vector<ArgumentInfo>& argsInfo,
+ const CallParams& callParams,
+ RenderContext& /* context */,
+ InternalValueMap& callArgs,
+ InternalValueMap& kwArgs,
+ InternalValueList& varArgs)
+{
+ bool isSucceeded = true;
+ ParsedArguments args = helpers::ParseCallParams(argsInfo, callParams, isSucceeded);
+
+ for (auto& a : args.args)
+ callArgs[a.first] = std::move(a.second);
+
+ for (auto& a : args.extraKwArgs)
+ kwArgs[a.first] = std::move(a.second);
+
+ for (auto& a : args.extraPosArgs)
+ varArgs.push_back(std::move(a));
+}
+
+void MacroStatement::SetupMacroScope(InternalValueMap&)
+{
+ ;
+}
+
+void MacroCallStatement::Render(OutStream& os, RenderContext& values)
+{
+ bool isMacroFound = false;
+ auto macroPtr = values.FindValue(m_macroName, isMacroFound);
+ if (!isMacroFound)
+ return;
+
+ auto& fnVal = macroPtr->second;
+ const Callable* callable = GetIf<Callable>(&fnVal);
+ if (callable == nullptr || callable->GetType() == Callable::Type::Expression)
+ return;
+
+ auto& curScope = values.GetCurrentScope();
+ auto callerP = curScope.find("caller");
+ bool hasCallerVal = callerP != curScope.end();
+ InternalValue prevCaller;
+ if (hasCallerVal)
+ prevCaller = callerP->second;
+
+ auto p = PrepareMacroParams(values);
+
+ curScope["caller"] = Callable(Callable::Macro, [this, params = std::move(p)](const CallParams& callParams, OutStream& stream, RenderContext& context) {
+ InvokeMacroRenderer(params, callParams, stream, context);
+ });
+
+ auto callParams = helpers::EvaluateCallParams(m_callParams, values);
+ callable->GetStatementCallable()(callParams, os, values);
+
+ if (hasCallerVal)
+ curScope["caller"] = prevCaller;
+ else
+ values.GetCurrentScope().erase("caller");
+}
+
+void MacroCallStatement::SetupMacroScope(InternalValueMap&) {}
+
+void DoStatement::Render(OutStream& /*os*/, RenderContext& values)
+{
+ m_expr->Evaluate(values);
+}
+
+void WithStatement::Render(OutStream& os, RenderContext& values)
+{
+ auto innerValues = values.Clone(true);
+ auto& scope = innerValues.EnterScope();
+
+ for (auto& var : m_scopeVars)
+ scope[var.first] = var.second->Evaluate(values);
+
+ m_mainBody->Render(os, innerValues);
+
+ innerValues.ExitScope();
+}
+
+void FilterStatement::Render(OutStream& os, RenderContext& values)
+{
+ TargetString arg;
+ auto argStream = values.GetRendererCallback()->GetStreamOnString(arg);
+ auto innerValues = values.Clone(true);
+ m_body->Render(argStream, innerValues);
+ const auto result = m_expr->Evaluate(std::move(arg), values);
+ os.WriteValue(result);
+}
+} // namespace jinja2
diff --git a/contrib/libs/jinja2cpp/src/statements.h b/contrib/libs/jinja2cpp/src/statements.h
new file mode 100644
index 0000000000..2a7db94a87
--- /dev/null
+++ b/contrib/libs/jinja2cpp/src/statements.h
@@ -0,0 +1,661 @@
+#ifndef JINJA2CPP_SRC_STATEMENTS_H
+#define JINJA2CPP_SRC_STATEMENTS_H
+
+#include "renderer.h"
+#include "expression_evaluator.h"
+
+#include <string>
+#include <vector>
+
+namespace jinja2
+{
+class Statement : public VisitableRendererBase
+{
+public:
+ VISITABLE_STATEMENT();
+};
+
+template<typename T = Statement>
+using StatementPtr = std::shared_ptr<T>;
+
+template<typename CharT>
+class TemplateImpl;
+
+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<MacroParam>;
+
+class ForStatement : public Statement
+{
+public:
+ VISITABLE_STATEMENT();
+
+ ForStatement(std::vector<std::string> vars, ExpressionEvaluatorPtr<> expr, ExpressionEvaluatorPtr<> ifExpr, bool isRecursive)
+ : m_vars(std::move(vars))
+ , m_value(expr)
+ , m_ifExpr(ifExpr)
+ , m_isRecursive(isRecursive)
+ {
+ }
+
+ void SetMainBody(RendererPtr renderer)
+ {
+ m_mainBody = std::move(renderer);
+ }
+
+ void SetElseBody(RendererPtr renderer)
+ {
+ m_elseBody = std::move(renderer);
+ }
+
+ void Render(OutStream& os, RenderContext& values) override;
+
+ bool IsEqual(const IComparable& other) const override
+ {
+ auto* val = dynamic_cast<const ForStatement*>(&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);
+ ListAdapter CreateFilteredAdapter(const ListAdapter& loopItems, RenderContext& values) const;
+
+private:
+ std::vector<std::string> m_vars;
+ ExpressionEvaluatorPtr<> m_value;
+ ExpressionEvaluatorPtr<> m_ifExpr;
+ bool m_isRecursive{};
+ RendererPtr m_mainBody;
+ RendererPtr m_elseBody;
+};
+
+class ElseBranchStatement;
+
+class IfStatement : public Statement
+{
+public:
+ VISITABLE_STATEMENT();
+
+ IfStatement(ExpressionEvaluatorPtr<> expr)
+ : m_expr(expr)
+ {
+ }
+
+ void SetMainBody(RendererPtr renderer)
+ {
+ m_mainBody = std::move(renderer);
+ }
+
+ void AddElseBranch(StatementPtr<ElseBranchStatement> branch)
+ {
+ m_elseBranches.push_back(branch);
+ }
+
+ void Render(OutStream& os, RenderContext& values) override;
+
+ bool IsEqual(const IComparable& other) const override
+ {
+ auto* val = dynamic_cast<const IfStatement*>(&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;
+ std::vector<StatementPtr<ElseBranchStatement>> m_elseBranches;
+};
+
+
+class ElseBranchStatement : public Statement
+{
+public:
+ VISITABLE_STATEMENT();
+
+ ElseBranchStatement(ExpressionEvaluatorPtr<> expr)
+ : m_expr(expr)
+ {
+ }
+
+ bool ShouldRender(RenderContext& values) const;
+ void SetMainBody(RendererPtr renderer)
+ {
+ m_mainBody = std::move(renderer);
+ }
+ void Render(OutStream& os, RenderContext& values) override;
+ bool IsEqual(const IComparable& other) const override
+ {
+ auto* val = dynamic_cast<const ElseBranchStatement*>(&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;
+ RendererPtr m_mainBody;
+};
+
+class SetStatement : public Statement
+{
+public:
+ SetStatement(std::vector<std::string> fields)
+ : m_fields(std::move(fields))
+ {
+ }
+
+ bool IsEqual(const IComparable& other) const override
+ {
+ auto* val = dynamic_cast<const SetStatement*>(&other);
+ if (!val)
+ return false;
+ if (m_fields != val->m_fields)
+ return false;
+ return true;
+ }
+protected:
+ void AssignBody(InternalValue, RenderContext&);
+
+private:
+ const std::vector<std::string> m_fields;
+};
+
+class SetLineStatement final : public SetStatement
+{
+public:
+ VISITABLE_STATEMENT();
+
+ SetLineStatement(std::vector<std::string> fields, ExpressionEvaluatorPtr<> expr)
+ : SetStatement(std::move(fields)), m_expr(std::move(expr))
+ {
+ }
+
+ void Render(OutStream& os, RenderContext& values) override;
+
+ bool IsEqual(const IComparable& other) const override
+ {
+ auto* val = dynamic_cast<const SetLineStatement*>(&other);
+ if (!val)
+ return false;
+ if (m_expr != val->m_expr)
+ return false;
+ return true;
+ }
+private:
+ const ExpressionEvaluatorPtr<> m_expr;
+};
+
+class SetBlockStatement : public SetStatement
+{
+public:
+ using SetStatement::SetStatement;
+
+ void SetBody(RendererPtr renderer)
+ {
+ m_body = std::move(renderer);
+ }
+
+ bool IsEqual(const IComparable& other) const override
+ {
+ auto* val = dynamic_cast<const SetBlockStatement*>(&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&);
+
+private:
+ RendererPtr m_body;
+};
+
+class SetRawBlockStatement final : public SetBlockStatement
+{
+public:
+ VISITABLE_STATEMENT();
+
+ using SetBlockStatement::SetBlockStatement;
+
+ void Render(OutStream&, RenderContext&) override;
+
+ bool IsEqual(const IComparable& other) const override
+ {
+ auto* val = dynamic_cast<const SetRawBlockStatement*>(&other);
+ if (!val)
+ return false;
+ if (!SetBlockStatement::IsEqual(*val))
+ return false;
+ return true;
+ }
+};
+
+class SetFilteredBlockStatement final : public SetBlockStatement
+{
+public:
+ VISITABLE_STATEMENT();
+
+ explicit SetFilteredBlockStatement(std::vector<std::string> fields, ExpressionEvaluatorPtr<ExpressionFilter> expr)
+ : SetBlockStatement(std::move(fields)), m_expr(std::move(expr))
+ {
+ }
+
+ void Render(OutStream&, RenderContext&) override;
+
+ bool IsEqual(const IComparable& other) const override
+ {
+ auto* val = dynamic_cast<const SetFilteredBlockStatement*>(&other);
+ if (!val)
+ return false;
+ if (!SetBlockStatement::IsEqual(*val))
+ return false;
+ if (m_expr != val->m_expr)
+ return false;
+ return true;
+ }
+
+private:
+ const ExpressionEvaluatorPtr<ExpressionFilter> m_expr;
+};
+
+class ParentBlockStatement : public Statement
+{
+public:
+ VISITABLE_STATEMENT();
+
+ ParentBlockStatement(std::string name, bool isScoped)
+ : m_name(std::move(name))
+ , m_isScoped(isScoped)
+ {
+ }
+
+ void SetMainBody(RendererPtr renderer)
+ {
+ m_mainBody = std::move(renderer);
+ }
+ void Render(OutStream &os, RenderContext &values) override;
+
+ bool IsEqual(const IComparable& other) const override
+ {
+ auto* val = dynamic_cast<const ParentBlockStatement*>(&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{};
+ RendererPtr m_mainBody;
+};
+
+class BlockStatement : public Statement
+{
+public:
+ VISITABLE_STATEMENT();
+
+ BlockStatement(std::string name)
+ : m_name(std::move(name))
+ {
+ }
+
+ auto& GetName() const {return m_name;}
+
+ void SetMainBody(RendererPtr renderer)
+ {
+ m_mainBody = std::move(renderer);
+ }
+ void Render(OutStream &os, RenderContext &values) override;
+
+ bool IsEqual(const IComparable& other) const override
+ {
+ auto* val = dynamic_cast<const BlockStatement*>(&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;
+};
+
+class ExtendsStatement : public Statement
+{
+public:
+ VISITABLE_STATEMENT();
+
+ using BlocksCollection = std::unordered_map<std::string, StatementPtr<BlockStatement>>;
+
+ ExtendsStatement(std::string name, bool isPath)
+ : m_templateName(std::move(name))
+ , m_isPath(isPath)
+ {
+ }
+
+ void Render(OutStream &os, RenderContext &values) override;
+ void AddBlock(StatementPtr<BlockStatement> block)
+ {
+ m_blocks[block->GetName()] = block;
+ }
+ bool IsEqual(const IComparable& other) const override
+ {
+ auto* val = dynamic_cast<const ExtendsStatement*>(&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{};
+ BlocksCollection m_blocks;
+ void DoRender(OutStream &os, RenderContext &values);
+};
+
+class IncludeStatement : public Statement
+{
+public:
+ VISITABLE_STATEMENT();
+
+ IncludeStatement(bool ignoreMissing, bool withContext)
+ : m_ignoreMissing(ignoreMissing)
+ , m_withContext(withContext)
+ {}
+
+ void SetIncludeNamesExpr(ExpressionEvaluatorPtr<> expr)
+ {
+ m_expr = std::move(expr);
+ }
+
+ void Render(OutStream& os, RenderContext& values) override;
+ bool IsEqual(const IComparable& other) const override
+ {
+ auto* val = dynamic_cast<const IncludeStatement*>(&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{};
+ ExpressionEvaluatorPtr<> m_expr;
+};
+
+class ImportStatement : public Statement
+{
+public:
+ VISITABLE_STATEMENT();
+
+ explicit ImportStatement(bool withContext)
+ : m_withContext(withContext)
+ {}
+
+ void SetImportNameExpr(ExpressionEvaluatorPtr<> expr)
+ {
+ m_nameExpr = std::move(expr);
+ }
+
+ void SetNamespace(std::string name)
+ {
+ m_namespace = std::move(name);
+ }
+
+ void AddNameToImport(std::string name, std::string alias)
+ {
+ m_namesToImport[std::move(name)] = std::move(alias);
+ }
+
+ void Render(OutStream& os, RenderContext& values) override;
+
+ bool IsEqual(const IComparable& other) const override
+ {
+ auto* val = dynamic_cast<const ImportStatement*>(&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{};
+ RendererPtr m_renderer;
+ ExpressionEvaluatorPtr<> m_nameExpr;
+ std::optional<std::string> m_namespace;
+ std::unordered_map<std::string, std::string> m_namesToImport;
+};
+
+class MacroStatement : public Statement
+{
+public:
+ VISITABLE_STATEMENT();
+
+ MacroStatement(std::string name, MacroParams params)
+ : m_name(std::move(name))
+ , m_params(std::move(params))
+ {
+ }
+
+ void SetMainBody(RendererPtr renderer)
+ {
+ m_mainBody = std::move(renderer);
+ }
+
+ void Render(OutStream &os, RenderContext &values) override;
+
+ bool IsEqual(const IComparable& other) const override
+ {
+ auto* val = dynamic_cast<const MacroStatement*>(&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<ArgumentInfo>& params, const CallParams& callParams, OutStream& stream, RenderContext& context);
+ void SetupCallArgs(const std::vector<ArgumentInfo>& argsInfo, const CallParams& callParams, RenderContext& context, InternalValueMap& callArgs, InternalValueMap& kwArgs, InternalValueList& varArgs);
+ virtual void SetupMacroScope(InternalValueMap& scope);
+ std::vector<ArgumentInfo> PrepareMacroParams(RenderContext& values);
+
+protected:
+ std::string m_name;
+ MacroParams m_params;
+ RendererPtr m_mainBody;
+};
+
+class MacroCallStatement : public MacroStatement
+{
+public:
+ VISITABLE_STATEMENT();
+
+ MacroCallStatement(std::string macroName, CallParamsInfo callParams, MacroParams callbackParams)
+ : MacroStatement("$call$", std::move(callbackParams))
+ , m_macroName(std::move(macroName))
+ , m_callParams(std::move(callParams))
+ {
+ }
+
+ void Render(OutStream &os, RenderContext &values) override;
+
+ bool IsEqual(const IComparable& other) const override
+ {
+ auto* val = dynamic_cast<const MacroCallStatement*>(&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;
+
+protected:
+ std::string m_macroName;
+ CallParamsInfo m_callParams;
+};
+
+class DoStatement : public Statement
+{
+public:
+ VISITABLE_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<const DoStatement*>(&other);
+ if (!val)
+ return false;
+ if (m_expr != val->m_expr)
+ return false;
+ return true;
+ }
+private:
+ ExpressionEvaluatorPtr<> m_expr;
+};
+
+class WithStatement : public Statement
+{
+public:
+ VISITABLE_STATEMENT();
+
+ void SetScopeVars(std::vector<std::pair<std::string, ExpressionEvaluatorPtr<>>> vars)
+ {
+ m_scopeVars = std::move(vars);
+ }
+ void SetMainBody(RendererPtr renderer)
+ {
+ m_mainBody = std::move(renderer);
+ }
+
+ void Render(OutStream &os, RenderContext &values) override;
+ bool IsEqual(const IComparable& other) const override
+ {
+ auto* val = dynamic_cast<const WithStatement*>(&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<std::pair<std::string, ExpressionEvaluatorPtr<>>> m_scopeVars;
+ RendererPtr m_mainBody;
+};
+
+class FilterStatement : public Statement
+{
+public:
+ VISITABLE_STATEMENT();
+
+ explicit FilterStatement(ExpressionEvaluatorPtr<ExpressionFilter> expr)
+ : m_expr(std::move(expr)) {}
+
+ void SetBody(RendererPtr renderer)
+ {
+ m_body = std::move(renderer);
+ }
+
+ void Render(OutStream &, RenderContext &) override;
+
+ bool IsEqual(const IComparable& other) const override
+ {
+ auto* val = dynamic_cast<const FilterStatement*>(&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<ExpressionFilter> m_expr;
+ RendererPtr m_body;
+};
+
+} // namespace jinja2
+
+#endif // JINJA2CPP_SRC_STATEMENTS_H
diff --git a/contrib/libs/jinja2cpp/src/string_converter_filter.cpp b/contrib/libs/jinja2cpp/src/string_converter_filter.cpp
new file mode 100644
index 0000000000..574fbe0464
--- /dev/null
+++ b/contrib/libs/jinja2cpp/src/string_converter_filter.cpp
@@ -0,0 +1,395 @@
+#include "filters.h"
+#include "testers.h"
+#include "value_visitors.h"
+#include "value_helpers.h"
+
+#include <algorithm>
+#include <numeric>
+#include <regex>
+#include <sstream>
+
+#include <boost/algorithm/string/trim_all.hpp>
+#include <boost/algorithm/string/replace.hpp>
+
+namespace ba = boost::algorithm;
+
+namespace jinja2
+{
+
+namespace filters
+{
+
+template<typename D>
+struct StringEncoder : public visitors::BaseVisitor<TargetString>
+{
+ using BaseVisitor::operator();
+
+ template<typename CharT>
+ TargetString operator() (const std::basic_string<CharT>& str) const
+ {
+ std::basic_string<CharT> result;
+
+ for (auto& ch : str)
+ {
+ static_cast<const D*>(this)->EncodeChar(ch, [&result](auto ... chs) {AppendChar(result, chs...);});
+ }
+
+ return TargetString(std::move(result));
+ }
+
+ template<typename CharT>
+ TargetString operator() (const std::basic_string_view<CharT>& str) const
+ {
+ std::basic_string<CharT> result;
+
+ for (auto& ch : str)
+ {
+ static_cast<const D*>(this)->EncodeChar(ch, [&result](auto ... chs) {AppendChar(result, chs...);});
+ }
+
+ return TargetString(std::move(result));
+ }
+
+ template<typename Str, typename CharT>
+ static void AppendChar(Str& str, CharT ch)
+ {
+ str.push_back(static_cast<typename Str::value_type>(ch));
+ }
+ template<typename Str, typename CharT, typename ... Args>
+ static void AppendChar(Str& str, CharT ch, Args ... chs)
+ {
+ str.push_back(static_cast<typename Str::value_type>(ch));
+ AppendChar(str, chs...);
+ }
+};
+
+template<typename Fn>
+struct GenericStringEncoder : public StringEncoder<GenericStringEncoder<Fn>>
+{
+ GenericStringEncoder(Fn fn) : m_fn(std::move(fn)) {}
+
+ template<typename CharT, typename AppendFn>
+ void EncodeChar(CharT ch, AppendFn&& fn) const
+ {
+ m_fn(ch, std::forward<AppendFn>(fn));
+ }
+
+ mutable Fn m_fn;
+};
+
+struct UrlStringEncoder : public StringEncoder<UrlStringEncoder>
+{
+ template<typename CharT, typename Fn>
+ void EncodeChar(CharT ch, Fn&& fn) const
+ {
+ enum EncodeStyle
+ {
+ None,
+ Percent
+ };
+
+ EncodeStyle encStyle = None;
+ switch (ch)
+ {
+ case ' ':
+ fn('+');
+ return;
+ case '+': case '\"': case '%': case '-':
+ case '!': case '#': case '$': case '&':
+ case '\'': case '(': case ')': case '*':
+ case ',': case '/': case ':': case ';':
+ case '=': case '?': case '@': case '[':
+ case ']':
+ encStyle = Percent;
+ break;
+ default:
+ if (AsUnsigned(ch) > 0x7f)
+ encStyle = Percent;
+ break;
+ }
+
+ if (encStyle == None)
+ {
+ fn(ch);
+ return;
+ }
+ union
+ {
+ uint32_t intCh;
+ uint8_t chars[4];
+ };
+ intCh = AsUnsigned(ch);
+ if (intCh > 0xffffff)
+ DoPercentEncoding(chars[3], fn);
+ if (intCh > 0xffff)
+ DoPercentEncoding(chars[2], fn);
+ if (intCh > 0xff)
+ DoPercentEncoding(chars[1], fn);
+ DoPercentEncoding(chars[0], fn);
+ }
+
+ template<typename Fn>
+ void DoPercentEncoding(uint8_t ch, Fn&& fn) const
+ {
+ char chars[] = "0123456789ABCDEF";
+ int ch1 = static_cast<int>(chars[(ch & 0xf0) >> 4]);
+ int ch2 = static_cast<int>(chars[ch & 0x0f]);
+ fn('%', ch1, ch2);
+ }
+
+ template<typename Ch, size_t SZ>
+ struct ToUnsigned;
+
+ template<typename Ch>
+ struct ToUnsigned<Ch, 1>
+ {
+ static auto Cast(Ch ch) {return static_cast<uint8_t>(ch);}
+ };
+
+ template<typename Ch>
+ struct ToUnsigned<Ch, 2>
+ {
+ static auto Cast(Ch ch) {return static_cast<uint16_t>(ch);}
+ };
+
+ template<typename Ch>
+ struct ToUnsigned<Ch, 4>
+ {
+ static auto Cast(Ch ch) {return static_cast<uint32_t>(ch);}
+ };
+
+ template<typename Ch>
+ auto AsUnsigned(Ch ch) const
+ {
+ return static_cast<uint32_t>(ToUnsigned<Ch, sizeof(Ch)>::Cast(ch));
+ }
+};
+
+StringConverter::StringConverter(FilterParams params, StringConverter::Mode mode)
+ : m_mode(mode)
+{
+ switch (m_mode)
+ {
+ case ReplaceMode:
+ ParseParams({{"old", true}, {"new", true}, {"count", false, static_cast<int64_t>(0)}}, params);
+ break;
+ case TruncateMode:
+ ParseParams({{"length", false, static_cast<int64_t>(255)}, {"killwords", false, false}, {"end", false, std::string("...")}, {"leeway", false}}, params);
+ break;
+ case CenterMode:
+ ParseParams({{"width", false, static_cast<int64_t>(80)}}, params);
+ break;
+ default: break;
+ }
+}
+
+InternalValue StringConverter::Filter(const InternalValue& baseVal, RenderContext& context)
+{
+ TargetString result;
+
+ auto isAlpha = ba::is_alpha();
+ auto isAlNum = ba::is_alnum();
+
+ switch (m_mode)
+ {
+ case TrimMode:
+ result = ApplyStringConverter(baseVal, [](auto strView) -> TargetString {
+ auto str = sv_to_string(strView);
+ ba::trim_all(str);
+ return TargetString(str);
+ });
+ break;
+ case TitleMode:
+ result = ApplyStringConverter<GenericStringEncoder>(baseVal, [isDelim = true, &isAlpha, &isAlNum](auto ch, auto&& fn) mutable {
+ if (isDelim && isAlpha(ch))
+ {
+ isDelim = false;
+ fn(std::toupper(ch, std::locale()));
+ return;
+ }
+
+ isDelim = !isAlNum(ch);
+ fn(ch);
+ });
+ break;
+ case WordCountMode:
+ {
+ int64_t wc = 0;
+ ApplyStringConverter<GenericStringEncoder>(baseVal, [isDelim = true, &wc, &isAlNum](auto ch, auto&&) mutable {
+ if (isDelim && isAlNum(ch))
+ {
+ isDelim = false;
+ wc ++;
+ return;
+ }
+ isDelim = !isAlNum(ch);
+ });
+ return InternalValue(wc);
+ }
+ case UpperMode:
+ result = ApplyStringConverter<GenericStringEncoder>(baseVal, [&isAlpha](auto ch, auto&& fn) mutable {
+ if (isAlpha(ch))
+ fn(std::toupper(ch, std::locale()));
+ else
+ fn(ch);
+ });
+ break;
+ case LowerMode:
+ result = ApplyStringConverter<GenericStringEncoder>(baseVal, [&isAlpha](auto ch, auto&& fn) mutable {
+ if (isAlpha(ch))
+ fn(std::tolower(ch, std::locale()));
+ else
+ fn(ch);
+ });
+ break;
+ case ReplaceMode:
+ result = ApplyStringConverter(baseVal, [this, &context](auto srcStr) -> TargetString {
+ std::decay_t<decltype(srcStr)> emptyStrView;
+ using CharT = typename decltype(emptyStrView)::value_type;
+ std::basic_string<CharT> emptyStr;
+ auto oldStr = GetAsSameString(srcStr, this->GetArgumentValue("old", context)).value_or(emptyStr);
+ auto newStr = GetAsSameString(srcStr, this->GetArgumentValue("new", context)).value_or(emptyStr);
+ auto count = ConvertToInt(this->GetArgumentValue("count", context));
+ auto str = sv_to_string(srcStr);
+ if (count == 0)
+ ba::replace_all(str, oldStr, newStr);
+ else
+ {
+ for (int64_t n = 0; n < count; ++ n)
+ ba::replace_first(str, oldStr, newStr);
+ }
+ return str;
+ });
+ break;
+ case TruncateMode:
+ result = ApplyStringConverter(baseVal, [this, &context, &isAlNum](auto srcStr) -> TargetString {
+ std::decay_t<decltype(srcStr)> emptyStrView;
+ using CharT = typename decltype(emptyStrView)::value_type;
+ std::basic_string<CharT> emptyStr;
+ auto length = ConvertToInt(this->GetArgumentValue("length", context));
+ auto killWords = ConvertToBool(this->GetArgumentValue("killwords", context));
+ auto end = GetAsSameString(srcStr, this->GetArgumentValue("end", context));
+ auto leeway = ConvertToInt(this->GetArgumentValue("leeway", context), 5);
+ if (static_cast<long long int>(srcStr.size()) <= length)
+ return sv_to_string(srcStr);
+
+ auto str = sv_to_string(srcStr);
+
+ if (killWords)
+ {
+ if (static_cast<long long int>(str.size()) > (length + leeway))
+ {
+ str.erase(str.begin() + static_cast<std::ptrdiff_t>(length), str.end());
+ str += end.value_or(emptyStr);
+ }
+ return str;
+ }
+
+ auto p = str.begin() + static_cast<std::ptrdiff_t>(length);
+ if (leeway != 0)
+ {
+ for (; leeway != 0 && p != str.end() && isAlNum(*p); -- leeway, ++ p);
+ if (p == str.end())
+ return TargetString(str);
+ }
+
+ if (isAlNum(*p))
+ {
+ for (; p != str.begin() && isAlNum(*p); -- p);
+ }
+ str.erase(p, str.end());
+ ba::trim_right(str);
+ str += end.value_or(emptyStr);
+
+ return TargetString(std::move(str));
+ });
+ break;
+ case UrlEncodeMode:
+ result = Apply<UrlStringEncoder>(baseVal);
+ break;
+ case CapitalMode:
+ result = ApplyStringConverter<GenericStringEncoder>(baseVal, [isFirstChar = true, &isAlpha](auto ch, auto&& fn) mutable {
+ if (isAlpha(ch))
+ {
+ if (isFirstChar)
+ fn(std::toupper(ch, std::locale()));
+ else
+ fn(std::tolower(ch, std::locale()));
+ }
+ else
+ fn(ch);
+
+ isFirstChar = false;
+ });
+ break;
+ case EscapeHtmlMode:
+ result = ApplyStringConverter<GenericStringEncoder>(baseVal, [](auto ch, auto&& fn) mutable {
+ switch(ch)
+ {
+ case '<':
+ fn('&', 'l', 't', ';');
+ break;
+ case '>':
+ fn('&', 'g', 't', ';');
+ break;
+ case '&':
+ fn('&', 'a', 'm', 'p', ';');
+ break;
+ case '\'':
+ fn('&', '#', '3', '9', ';');
+ break;
+ case '\"':
+ fn('&', '#', '3', '4', ';');
+ break;
+ default:
+ fn(ch);
+ break;
+ }
+ });
+ break;
+ case StriptagsMode:
+ result = ApplyStringConverter(baseVal, [](auto srcStr) -> TargetString {
+ auto str = sv_to_string(srcStr);
+ using StringT = decltype(str);
+ using CharT = typename StringT::value_type;
+ static const std::basic_regex<CharT> STRIPTAGS_RE(UNIVERSAL_STR("(<!--.*?-->|<[^>]*>)").GetValueStr<CharT>());
+ str = std::regex_replace(str, STRIPTAGS_RE, UNIVERSAL_STR("").GetValueStr<CharT>());
+ ba::trim_all(str);
+ static const StringT html_entities [] {
+ UNIVERSAL_STR("&amp;").GetValueStr<CharT>(), UNIVERSAL_STR("&").GetValueStr<CharT>(),
+ UNIVERSAL_STR("&apos;").GetValueStr<CharT>(), UNIVERSAL_STR("\'").GetValueStr<CharT>(),
+ UNIVERSAL_STR("&gt;").GetValueStr<CharT>(), UNIVERSAL_STR(">").GetValueStr<CharT>(),
+ UNIVERSAL_STR("&lt;").GetValueStr<CharT>(), UNIVERSAL_STR("<").GetValueStr<CharT>(),
+ UNIVERSAL_STR("&quot;").GetValueStr<CharT>(), UNIVERSAL_STR("\"").GetValueStr<CharT>(),
+ UNIVERSAL_STR("&#39;").GetValueStr<CharT>(), UNIVERSAL_STR("\'").GetValueStr<CharT>(),
+ UNIVERSAL_STR("&#34;").GetValueStr<CharT>(), UNIVERSAL_STR("\"").GetValueStr<CharT>(),
+ };
+ for (auto it = std::begin(html_entities), end = std::end(html_entities); it < end; it += 2)
+ {
+ ba::replace_all(str, *it, *(it + 1));
+ }
+ return str;
+ });
+ break;
+ case CenterMode:
+ result = ApplyStringConverter(baseVal, [this, &context](auto srcStr) -> TargetString {
+ auto width = ConvertToInt(this->GetArgumentValue("width", context));
+ auto str = sv_to_string(srcStr);
+ auto string_length = static_cast<long long int>(str.size());
+ if (string_length >= width)
+ return str;
+ auto whitespaces = width - string_length;
+ str = decltype(str)(static_cast<std::string::size_type>(whitespaces + 1) / 2, ' ') + str;
+ str.append(static_cast<std::string::size_type>(whitespaces / 2), ' ');
+ return TargetString(std::move(str));
+ });
+ break;
+ default:
+ break;
+ }
+
+ return std::move(result);
+}
+
+} // namespace filters
+} // namespace jinja2
diff --git a/contrib/libs/jinja2cpp/src/template.cpp b/contrib/libs/jinja2cpp/src/template.cpp
new file mode 100644
index 0000000000..2734387508
--- /dev/null
+++ b/contrib/libs/jinja2cpp/src/template.cpp
@@ -0,0 +1,190 @@
+#include "jinja2cpp/template.h"
+#include "template_impl.h"
+
+#include <fmt/format.h>
+
+#include <fstream>
+#include <sstream>
+
+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<typename CharT>
+auto GetImpl(std::shared_ptr<ITemplateImpl> impl)
+{
+ return static_cast<TemplateImpl<CharT>*>(impl.get());
+}
+
+Template::Template(TemplateEnv* env)
+ : m_impl(new TemplateImpl<char>(env))
+{
+
+}
+
+Template::~Template() = default;
+
+Result<void> Template::Load(const char* tpl, std::string tplName)
+{
+ std::string t(tpl);
+ auto result = GetImpl<char>(m_impl)->Load(std::move(t), std::move(tplName));
+ return !result ? Result<void>() : nonstd::make_unexpected(std::move(result.get()));
+}
+
+Result<void> Template::Load(const std::string& str, std::string tplName)
+{
+ auto result = GetImpl<char>(m_impl)->Load(str, std::move(tplName));
+ return !result ? Result<void>() : nonstd::make_unexpected(std::move(result.get()));
+}
+
+Result<void> Template::Load(std::istream& stream, std::string tplName)
+{
+ std::string t;
+
+ while (stream.good() && !stream.eof())
+ {
+ char buff[0x10000];
+ stream.read(buff, sizeof(buff));
+ auto read = stream.gcount();
+ if (read)
+ t.append(buff, buff + read);
+ }
+
+ auto result = GetImpl<char>(m_impl)->Load(std::move(t), std::move(tplName));
+ return !result ? Result<void>() : nonstd::make_unexpected(std::move(result.get()));
+}
+
+Result<void> Template::LoadFromFile(const std::string& fileName)
+{
+ std::ifstream file(fileName);
+
+ if (!file.good())
+ return Result<void>();
+
+ return Load(file, fileName);
+}
+
+Result<void> Template::Render(std::ostream& os, const jinja2::ValuesMap& params)
+{
+ std::string buffer;
+ auto result = GetImpl<char>(m_impl)->Render(buffer, params);
+
+ if (!result)
+ os.write(buffer.data(), buffer.size());
+
+ return !result ? Result<void>() : nonstd::make_unexpected(std::move(result.get()));
+}
+
+Result<std::string> Template::RenderAsString(const jinja2::ValuesMap& params)
+{
+ std::string buffer;
+ auto result = GetImpl<char>(m_impl)->Render(buffer, params);
+ return !result ? Result<std::string>(std::move(buffer)) : Result<std::string>(nonstd::make_unexpected(std::move(result.get())));;
+}
+
+Result<GenericMap> Template::GetMetadata()
+{
+ return GetImpl<char>(m_impl)->GetMetadata();
+}
+
+Result<MetadataInfo<char>> Template::GetMetadataRaw()
+{
+ return GetImpl<char>(m_impl)->GetMetadataRaw();
+}
+
+bool Template::IsEqual(const Template& other) const
+{
+ return m_impl == other.m_impl;
+}
+
+TemplateW::TemplateW(TemplateEnv* env)
+ : m_impl(new TemplateImpl<wchar_t>(env))
+{
+
+}
+
+TemplateW::~TemplateW() = default;
+
+ResultW<void> TemplateW::Load(const wchar_t* tpl, std::string tplName)
+{
+ std::wstring t(tpl);
+ auto result = GetImpl<wchar_t>(m_impl)->Load(t, std::move(tplName));
+ return !result ? ResultW<void>() : nonstd::make_unexpected(std::move(result.get()));
+}
+
+ResultW<void> TemplateW::Load(const std::wstring& str, std::string tplName)
+{
+ auto result = GetImpl<wchar_t>(m_impl)->Load(str, std::move(tplName));
+ return !result ? ResultW<void>() : nonstd::make_unexpected(std::move(result.get()));
+}
+
+ResultW<void> TemplateW::Load(std::wistream& stream, std::string tplName)
+{
+ std::wstring t;
+
+ while (stream.good() && !stream.eof())
+ {
+ wchar_t buff[0x10000];
+ stream.read(buff, sizeof(buff));
+ auto read = stream.gcount();
+ if (read)
+ t.append(buff, buff + read);
+ }
+
+ auto result = GetImpl<wchar_t>(m_impl)->Load(t, std::move(tplName));
+ return !result ? ResultW<void>() : nonstd::make_unexpected(std::move(result.get()));
+}
+
+ResultW<void> TemplateW::LoadFromFile(const std::string& fileName)
+{
+ std::wifstream file(fileName);
+
+ if (!file.good())
+ return ResultW<void>();
+
+ return Load(file, fileName);
+}
+
+ResultW<void> TemplateW::Render(std::wostream& os, const jinja2::ValuesMap& params)
+{
+ std::wstring buffer;
+ auto result = GetImpl<wchar_t>(m_impl)->Render(buffer, params);
+ if (!result)
+ os.write(buffer.data(), buffer.size());
+ return !result ? ResultW<void>() : ResultW<void>(nonstd::make_unexpected(std::move(result.get())));
+}
+
+ResultW<std::wstring> TemplateW::RenderAsString(const jinja2::ValuesMap& params)
+{
+ std::wstring buffer;
+ auto result = GetImpl<wchar_t>(m_impl)->Render(buffer, params);
+
+ return !result ? buffer : ResultW<std::wstring>(nonstd::make_unexpected(std::move(result.get())));
+}
+
+ResultW<GenericMap> TemplateW::GetMetadata()
+{
+ return GenericMap();
+ // GetImpl<wchar_t>(m_impl)->GetMetadata();
+}
+
+ResultW<MetadataInfo<wchar_t>> TemplateW::GetMetadataRaw()
+{
+ return MetadataInfo<wchar_t>();
+ // GetImpl<wchar_t>(m_impl)->GetMetadataRaw();
+ ;
+}
+bool TemplateW::IsEqual(const TemplateW& other) const
+{
+ return m_impl == other.m_impl;
+}
+
+} // namespace jinja2
diff --git a/contrib/libs/jinja2cpp/src/template_env.cpp b/contrib/libs/jinja2cpp/src/template_env.cpp
new file mode 100644
index 0000000000..239a4f02e1
--- /dev/null
+++ b/contrib/libs/jinja2cpp/src/template_env.cpp
@@ -0,0 +1,95 @@
+#include <jinja2cpp/template.h>
+#include <jinja2cpp/template_env.h>
+
+namespace jinja2
+{
+template<typename CharT>
+struct TemplateFunctions;
+
+template<>
+struct TemplateFunctions<char>
+{
+ using ResultType = nonstd::expected<Template, ErrorInfo>;
+ static Template CreateTemplate(TemplateEnv* env) { return Template(env); }
+ static auto LoadFile(const std::string& fileName, const IFilesystemHandler* fs) { return fs->OpenStream(fileName); }
+};
+
+template<>
+struct TemplateFunctions<wchar_t>
+{
+ using ResultType = nonstd::expected<TemplateW, ErrorInfoW>;
+ static TemplateW CreateTemplate(TemplateEnv* env) { return TemplateW(env); }
+ static auto LoadFile(const std::string& fileName, const IFilesystemHandler* fs) { return fs->OpenWStream(fileName); }
+};
+
+template<typename CharT, typename T, typename Cache>
+auto TemplateEnv::LoadTemplateImpl(TemplateEnv* env, std::string fileName, const T& filesystemHandlers, Cache& cache)
+{
+ using Functions = TemplateFunctions<CharT>;
+ using ResultType = typename Functions::ResultType;
+ using ErrorType = typename ResultType::error_type;
+ auto tpl = Functions::CreateTemplate(env);
+
+ {
+ std::shared_lock<std::shared_timed_mutex> l(m_guard);
+ auto p = cache.find(fileName);
+ if (p != cache.end())
+ {
+ if (m_settings.autoReload)
+ {
+ auto lastModified = p->second.handler->GetLastModificationDate(fileName);
+ if (!lastModified || (p->second.lastModification && lastModified.value() <= p->second.lastModification.value()))
+ return ResultType(p->second.tpl);
+ }
+ else
+ return ResultType(p->second.tpl);
+ }
+ }
+
+ for (auto& fh : filesystemHandlers)
+ {
+ if (!fh.prefix.empty() && fileName.find(fh.prefix) != 0)
+ continue;
+
+ auto stream = Functions::LoadFile(fileName, fh.handler.get());
+ if (stream)
+ {
+ auto res = tpl.Load(*stream, fileName);
+ if (!res)
+ return ResultType(res.get_unexpected());
+
+ if (m_settings.cacheSize != 0)
+ {
+ auto lastModified = fh.handler->GetLastModificationDate(fileName);
+ std::unique_lock<std::shared_timed_mutex> l(m_guard);
+ auto& cacheEntry = cache[fileName];
+ cacheEntry.tpl = tpl;
+ cacheEntry.handler = fh.handler;
+ cacheEntry.lastModification = lastModified;
+ }
+
+ return ResultType(tpl);
+ }
+ }
+
+ typename ErrorType::Data errorData;
+ errorData.code = ErrorCode::FileNotFound;
+ errorData.srcLoc.col = 1;
+ errorData.srcLoc.line = 1;
+ errorData.srcLoc.fileName = "";
+ errorData.extraParams.push_back(Value(fileName));
+
+ return ResultType(nonstd::make_unexpected(ErrorType(errorData)));
+}
+
+nonstd::expected<Template, ErrorInfo> TemplateEnv::LoadTemplate(std::string fileName)
+{
+ return LoadTemplateImpl<char>(this, std::move(fileName), m_filesystemHandlers, m_templateCache);
+}
+
+nonstd::expected<TemplateW, ErrorInfoW> TemplateEnv::LoadTemplateW(std::string fileName)
+{
+ return LoadTemplateImpl<wchar_t>(this, std::move(fileName), m_filesystemHandlers, m_templateWCache);
+}
+
+} // namespace jinja2
diff --git a/contrib/libs/jinja2cpp/src/template_impl.h b/contrib/libs/jinja2cpp/src/template_impl.h
new file mode 100644
index 0000000000..cc9c9a2a42
--- /dev/null
+++ b/contrib/libs/jinja2cpp/src/template_impl.h
@@ -0,0 +1,487 @@
+#ifndef JINJA2CPP_SRC_TEMPLATE_IMPL_H
+#define JINJA2CPP_SRC_TEMPLATE_IMPL_H
+
+#include "internal_value.h"
+#include "jinja2cpp/binding/rapid_json.h"
+#include "jinja2cpp/template_env.h"
+#include "jinja2cpp/value.h"
+#include "renderer.h"
+#include "template_parser.h"
+#include "value_visitors.h"
+
+#include <boost/optional.hpp>
+#include <boost/predef/other/endian.h>
+#include <contrib/restricted/expected-lite/include/nonstd/expected.hpp>
+#include <rapidjson/error/en.h>
+
+#include <string>
+
+namespace jinja2
+{
+namespace detail
+{
+template<size_t Sz>
+struct RapidJsonEncodingType;
+
+template<>
+struct RapidJsonEncodingType<1>
+{
+ using type = rapidjson::UTF8<char>;
+};
+
+#ifdef BOOST_ENDIAN_BIG_BYTE
+template<>
+struct RapidJsonEncodingType<2>
+{
+ using type = rapidjson::UTF16BE<wchar_t>;
+};
+
+template<>
+struct RapidJsonEncodingType<4>
+{
+ using type = rapidjson::UTF32BE<wchar_t>;
+};
+#else
+template<>
+struct RapidJsonEncodingType<2>
+{
+ using type = rapidjson::UTF16LE<wchar_t>;
+};
+
+template<>
+struct RapidJsonEncodingType<4>
+{
+ using type = rapidjson::UTF32LE<wchar_t>;
+};
+#endif
+} // namespace detail
+
+extern void SetupGlobals(InternalValueMap& globalParams);
+
+class ITemplateImpl
+{
+public:
+ virtual ~ITemplateImpl() = default;
+};
+
+
+template<typename U>
+struct TemplateLoader;
+
+template<>
+struct TemplateLoader<char>
+{
+ static auto Load(const std::string& fileName, TemplateEnv* env)
+ {
+ return env->LoadTemplate(fileName);
+ }
+};
+
+template<>
+struct TemplateLoader<wchar_t>
+{
+ static auto Load(const std::string& fileName, TemplateEnv* env)
+ {
+ return env->LoadTemplateW(fileName);
+ }
+};
+
+template<typename CharT>
+class GenericStreamWriter : public OutStream::StreamWriter
+{
+public:
+ explicit GenericStreamWriter(std::basic_string<CharT>& os)
+ : m_os(os)
+ {}
+
+ // StreamWriter interface
+ void WriteBuffer(const void* ptr, size_t length) override
+ {
+ m_os.append(reinterpret_cast<const CharT*>(ptr), length);
+ }
+ void WriteValue(const InternalValue& val) override
+ {
+ Apply<visitors::ValueRenderer<CharT>>(val, m_os);
+ }
+
+private:
+ std::basic_string<CharT>& m_os;
+};
+
+template<typename CharT>
+class StringStreamWriter : public OutStream::StreamWriter
+{
+public:
+ explicit StringStreamWriter(std::basic_string<CharT>* targetStr)
+ : m_targetStr(targetStr)
+ {}
+
+ // StreamWriter interface
+ void WriteBuffer(const void* ptr, size_t length) override
+ {
+ m_targetStr->append(reinterpret_cast<const CharT*>(ptr), length);
+ // m_os.write(reinterpret_cast<const CharT*>(ptr), length);
+ }
+ void WriteValue(const InternalValue& val) override
+ {
+ Apply<visitors::ValueRenderer<CharT>>(val, *m_targetStr);
+ }
+
+private:
+ std::basic_string<CharT>* m_targetStr;
+};
+
+template<typename ErrorTpl1, typename ErrorTpl2>
+struct ErrorConverter;
+
+template<typename CharT1, typename CharT2>
+struct ErrorConverter<ErrorInfoTpl<CharT1>, ErrorInfoTpl<CharT2>>
+{
+ static ErrorInfoTpl<CharT1> Convert(const ErrorInfoTpl<CharT2>& srcError)
+ {
+ typename ErrorInfoTpl<CharT1>::Data errorData;
+ errorData.code = srcError.GetCode();
+ errorData.srcLoc = srcError.GetErrorLocation();
+ errorData.locationDescr = ConvertString<std::basic_string<CharT1>>(srcError.GetLocationDescr());
+ errorData.extraParams = srcError.GetExtraParams();
+
+ return ErrorInfoTpl<CharT1>(errorData);
+ }
+};
+
+template<typename CharT>
+struct ErrorConverter<ErrorInfoTpl<CharT>, ErrorInfoTpl<CharT>>
+{
+ static const ErrorInfoTpl<CharT>& Convert(const ErrorInfoTpl<CharT>& srcError)
+ {
+ return srcError;
+ }
+};
+
+template<typename CharT>
+inline bool operator==(const MetadataInfo<CharT>& lhs, const MetadataInfo<CharT>& 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<typename CharT>
+inline bool operator!=(const MetadataInfo<CharT>& lhs, const MetadataInfo<CharT>& 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<typename CharT>
+class TemplateImpl : public ITemplateImpl
+{
+public:
+ using ThisType = TemplateImpl<CharT>;
+
+ explicit TemplateImpl(TemplateEnv* env)
+ : m_env(env)
+ {
+ if (env)
+ m_settings = env->GetSettings();
+ }
+
+ auto GetRenderer() const {return m_renderer;}
+ auto GetTemplateName() const {};
+
+ boost::optional<ErrorInfoTpl<CharT>> Load(std::basic_string<CharT> tpl, std::string tplName)
+ {
+ m_template = std::move(tpl);
+ m_templateName = tplName.empty() ? std::string("noname.j2tpl") : std::move(tplName);
+ TemplateParser<CharT> parser(&m_template, m_settings, m_env, m_templateName);
+
+ auto parseResult = parser.Parse();
+ if (!parseResult)
+ return parseResult.error()[0];
+
+ m_renderer = *parseResult;
+ m_metadataInfo = parser.GetMetadataInfo();
+ return boost::optional<ErrorInfoTpl<CharT>>();
+ }
+
+ boost::optional<ErrorInfoTpl<CharT>> Render(std::basic_string<CharT>& os, const ValuesMap& params)
+ {
+ boost::optional<ErrorInfoTpl<CharT>> normalResult;
+
+ if (!m_renderer)
+ {
+ typename ErrorInfoTpl<CharT>::Data errorData;
+ errorData.code = ErrorCode::TemplateNotParsed;
+ errorData.srcLoc.col = 1;
+ errorData.srcLoc.line = 1;
+ errorData.srcLoc.fileName = "<unknown file>";
+
+ return ErrorInfoTpl<CharT>(errorData);
+ }
+
+ try
+ {
+ InternalValueMap extParams;
+ InternalValueMap intParams;
+
+ auto convertFn = [&intParams](const auto& params) {
+ for (auto& ip : params)
+ {
+ auto valRef = &ip.second.data();
+ auto newParam = visit(visitors::InputValueConvertor(false, true), *valRef);
+ if (!newParam)
+ intParams[ip.first] = ValueRef(static_cast<const Value&>(*valRef));
+ else
+ intParams[ip.first] = newParam.get();
+ }
+ };
+
+ if (m_env)
+ {
+ m_env->ApplyGlobals(convertFn);
+ std::swap(extParams, intParams);
+ }
+
+ convertFn(params);
+ SetupGlobals(extParams);
+
+ RendererCallback callback(this);
+ RenderContext context(intParams, extParams, &callback);
+ InitRenderContext(context);
+ OutStream outStream([writer = GenericStreamWriter<CharT>(os)]() mutable -> OutStream::StreamWriter* {return &writer;});
+ m_renderer->Render(outStream, context);
+ }
+ catch (const ErrorInfoTpl<char>& error)
+ {
+ return ErrorConverter<ErrorInfoTpl<CharT>, ErrorInfoTpl<char>>::Convert(error);
+ }
+ catch (const ErrorInfoTpl<wchar_t>& error)
+ {
+ return ErrorConverter<ErrorInfoTpl<CharT>, ErrorInfoTpl<wchar_t>>::Convert(error);
+ }
+ catch (const std::exception& ex)
+ {
+ typename ErrorInfoTpl<CharT>::Data errorData;
+ errorData.code = ErrorCode::UnexpectedException;
+ errorData.srcLoc.col = 1;
+ errorData.srcLoc.line = 1;
+ errorData.srcLoc.fileName = m_templateName;
+ errorData.extraParams.push_back(Value(std::string(ex.what())));
+
+ return ErrorInfoTpl<CharT>(errorData);
+ }
+
+ return normalResult;
+ }
+
+ InternalValueMap& InitRenderContext(RenderContext& context)
+ {
+ auto& curScope = context.GetCurrentScope();
+ return curScope;
+ }
+
+ using TplLoadResultType = std::variant<EmptyValue,
+ nonstd::expected<std::shared_ptr<TemplateImpl<char>>, ErrorInfo>,
+ nonstd::expected<std::shared_ptr<TemplateImpl<wchar_t>>, ErrorInfoW>>;
+
+ using TplOrError = nonstd::expected<std::shared_ptr<TemplateImpl<CharT>>, ErrorInfoTpl<CharT>>;
+
+ TplLoadResultType LoadTemplate(const std::string& fileName)
+ {
+ if (!m_env)
+ return TplLoadResultType(EmptyValue());
+
+ auto tplWrapper = TemplateLoader<CharT>::Load(fileName, m_env);
+ if (!tplWrapper)
+ return TplLoadResultType(TplOrError(tplWrapper.get_unexpected()));
+
+ return TplLoadResultType(TplOrError(std::static_pointer_cast<ThisType>(tplWrapper.value().m_impl)));
+ }
+
+ TplLoadResultType LoadTemplate(const InternalValue& fileName)
+ {
+ auto name = GetAsSameString(std::string(), fileName);
+ if (!name)
+ {
+ typename ErrorInfoTpl<CharT>::Data errorData;
+ errorData.code = ErrorCode::InvalidTemplateName;
+ errorData.srcLoc.col = 1;
+ errorData.srcLoc.line = 1;
+ errorData.srcLoc.fileName = m_templateName;
+ errorData.extraParams.push_back(IntValue2Value(fileName));
+ return TplOrError(nonstd::make_unexpected(ErrorInfoTpl<CharT>(errorData)));
+ }
+
+ return LoadTemplate(name.value());
+ }
+
+ nonstd::expected<GenericMap, ErrorInfoTpl<CharT>> GetMetadata() const
+ {
+ auto& metadataString = m_metadataInfo.metadata;
+ if (metadataString.empty())
+ return GenericMap();
+
+ if (m_metadataInfo.metadataType == "json")
+ {
+ m_metadataJson = JsonDocumentType();
+ rapidjson::ParseResult res = m_metadataJson.value().Parse(metadataString.data(), metadataString.size());
+ if (!res)
+ {
+ typename ErrorInfoTpl<CharT>::Data errorData;
+ errorData.code = ErrorCode::MetadataParseError;
+ errorData.srcLoc = m_metadataInfo.location;
+ std::string jsonError = rapidjson::GetParseError_En(res.Code());
+ errorData.extraParams.push_back(Value(std::move(jsonError)));
+ return nonstd::make_unexpected(ErrorInfoTpl<CharT>(errorData));
+ }
+ m_metadata = std::move(std::get<GenericMap>(Reflect(m_metadataJson.value()).data()));
+ return m_metadata.value();
+ }
+ return GenericMap();
+ }
+
+ nonstd::expected<MetadataInfo<CharT>, ErrorInfoTpl<CharT>> GetMetadataRaw() const { return m_metadataInfo; }
+
+ bool operator==(const TemplateImpl<CharT>& 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)
+ {
+ typename ErrorInfoTpl<CharT>::Data errorData;
+ errorData.code = code;
+ errorData.srcLoc.col = 1;
+ errorData.srcLoc.line = 1;
+ errorData.srcLoc.fileName = m_templateName;
+ errorData.extraParams = std::move(extraParams);
+
+ throw ErrorInfoTpl<CharT>(std::move(errorData));
+ }
+
+ class RendererCallback : public IRendererCallback
+ {
+ public:
+ explicit RendererCallback(ThisType* host)
+ : m_host(host)
+ {}
+
+ TargetString GetAsTargetString(const InternalValue& val) override
+ {
+ std::basic_string<CharT> os;
+ Apply<visitors::ValueRenderer<CharT>>(val, os);
+ return TargetString(std::move(os));
+ }
+
+ OutStream GetStreamOnString(TargetString& str) override
+ {
+ using string_t = std::basic_string<CharT>;
+ str = string_t();
+ return OutStream([writer = StringStreamWriter<CharT>(&std::get<string_t>(str))]() mutable -> OutStream::StreamWriter* { return &writer; });
+ }
+
+ std::variant<EmptyValue,
+ nonstd::expected<std::shared_ptr<TemplateImpl<char>>, ErrorInfo>,
+ nonstd::expected<std::shared_ptr<TemplateImpl<wchar_t>>, ErrorInfoW>> LoadTemplate(const std::string& fileName) const override
+ {
+ return m_host->LoadTemplate(fileName);
+ }
+
+ std::variant<EmptyValue,
+ nonstd::expected<std::shared_ptr<TemplateImpl<char>>, ErrorInfo>,
+ nonstd::expected<std::shared_ptr<TemplateImpl<wchar_t>>, ErrorInfoW>> LoadTemplate(const InternalValue& fileName) const override
+ {
+ return m_host->LoadTemplate(fileName);
+ }
+
+ void ThrowRuntimeError(ErrorCode code, ValuesList extraParams) override
+ {
+ m_host->ThrowRuntimeError(code, std::move(extraParams));
+ }
+
+ bool IsEqual(const IComparable& other) const override
+ {
+ auto* callback = dynamic_cast<const RendererCallback*>(&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<const RendererCallback*>(&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<typename detail::RapidJsonEncodingType<sizeof(CharT)>::type>;
+
+ TemplateEnv* m_env{};
+ Settings m_settings;
+ std::basic_string<CharT> m_template;
+ std::string m_templateName;
+ RendererPtr m_renderer;
+ mutable std::optional<GenericMap> m_metadata;
+ mutable std::optional<JsonDocumentType> m_metadataJson;
+ MetadataInfo<CharT> m_metadataInfo;
+};
+
+} // namespace jinja2
+
+#endif // JINJA2CPP_SRC_TEMPLATE_IMPL_H
diff --git a/contrib/libs/jinja2cpp/src/template_parser.cpp b/contrib/libs/jinja2cpp/src/template_parser.cpp
new file mode 100644
index 0000000000..bd09039341
--- /dev/null
+++ b/contrib/libs/jinja2cpp/src/template_parser.cpp
@@ -0,0 +1,972 @@
+#include "template_parser.h"
+#include "renderer.h"
+#include <boost/cast.hpp>
+
+namespace jinja2
+{
+
+StatementsParser::ParseResult StatementsParser::Parse(LexScanner& lexer, StatementInfoList& statementsInfo)
+{
+ Token tok = lexer.NextToken();
+ ParseResult result;
+
+ switch (lexer.GetAsKeyword(tok))
+ {
+ case Keyword::For:
+ result = ParseFor(lexer, statementsInfo, tok);
+ break;
+ case Keyword::Endfor:
+ result = ParseEndFor(lexer, statementsInfo, tok);
+ break;
+ case Keyword::If:
+ result = ParseIf(lexer, statementsInfo, tok);
+ break;
+ case Keyword::Else:
+ result = ParseElse(lexer, statementsInfo, tok);
+ break;
+ case Keyword::ElIf:
+ result = ParseElIf(lexer, statementsInfo, tok);
+ break;
+ case Keyword::EndIf:
+ result = ParseEndIf(lexer, statementsInfo, tok);
+ break;
+ case Keyword::Set:
+ result = ParseSet(lexer, statementsInfo, tok);
+ break;
+ case Keyword::EndSet:
+ result = ParseEndSet(lexer, statementsInfo, tok);
+ break;
+ case Keyword::Block:
+ result = ParseBlock(lexer, statementsInfo, tok);
+ break;
+ case Keyword::EndBlock:
+ result = ParseEndBlock(lexer, statementsInfo, tok);
+ break;
+ case Keyword::Extends:
+ result = ParseExtends(lexer, statementsInfo, tok);
+ break;
+ case Keyword::Macro:
+ result = ParseMacro(lexer, statementsInfo, tok);
+ break;
+ case Keyword::EndMacro:
+ result = ParseEndMacro(lexer, statementsInfo, tok);
+ break;
+ case Keyword::Call:
+ result = ParseCall(lexer, statementsInfo, tok);
+ break;
+ case Keyword::EndCall:
+ result = ParseEndCall(lexer, statementsInfo, tok);
+ break;
+ case Keyword::Include:
+ result = ParseInclude(lexer, statementsInfo, tok);
+ break;
+ case Keyword::Import:
+ result = ParseImport(lexer, statementsInfo, tok);
+ break;
+ case Keyword::From:
+ result = ParseFrom(lexer, statementsInfo, tok);
+ break;
+ case Keyword::Do:
+ if (!m_settings.extensions.Do)
+ return MakeParseError(ErrorCode::ExtensionDisabled, tok);
+ result = ParseDo(lexer, statementsInfo, tok);
+ break;
+ case Keyword::With:
+ result = ParseWith(lexer, statementsInfo, tok);
+ break;
+ case Keyword::EndWith:
+ result = ParseEndWith(lexer, statementsInfo, tok);
+ break;
+ case Keyword::Filter:
+ result = ParseFilter(lexer, statementsInfo, tok);
+ break;
+ case Keyword::EndFilter:
+ result = ParseEndFilter(lexer, statementsInfo, tok);
+ break;
+ default:
+ return MakeParseError(ErrorCode::UnexpectedToken, tok);
+ }
+
+ if (result)
+ {
+ tok = lexer.PeekNextToken();
+ if (tok != Token::Eof)
+ return MakeParseError(ErrorCode::ExpectedEndOfStatement, tok);
+ }
+
+ return result;
+}
+
+struct ErrorTokenConverter
+{
+ const Token& baseTok;
+
+ explicit ErrorTokenConverter(const Token& t)
+ : baseTok(t)
+ {}
+
+ Token operator()(const Token& tok) const
+ {
+ return tok;
+ }
+
+ template<typename T>
+ Token operator()(T tokType) const
+ {
+ auto newTok = baseTok;
+ newTok.type = static_cast<Token::Type>(tokType);
+ if (newTok.type == Token::Identifier || newTok.type == Token::String)
+ newTok.range.endOffset = newTok.range.startOffset;
+ return newTok;
+ }
+};
+
+template<typename ... Args>
+auto MakeParseErrorTL(ErrorCode code, const Token& baseTok, Args ... expectedTokens)
+{
+ ErrorTokenConverter tokCvt(baseTok);
+
+ return MakeParseError(code, baseTok, {tokCvt(expectedTokens)...});
+}
+
+StatementsParser::ParseResult StatementsParser::ParseFor(LexScanner &lexer, StatementInfoList &statementsInfo,
+ const Token &stmtTok)
+{
+ std::vector<std::string> vars;
+
+ while (lexer.PeekNextToken() == Token::Identifier)
+ {
+ auto tok = lexer.NextToken();
+ vars.push_back(AsString(tok.value));
+ if (lexer.NextToken() != ',')
+ {
+ lexer.ReturnToken();
+ break;
+ }
+ }
+
+ if (vars.empty())
+ return MakeParseError(ErrorCode::ExpectedIdentifier, lexer.PeekNextToken());
+
+ if (!lexer.EatIfEqual(Keyword::In))
+ {
+ Token tok1 = lexer.PeekNextToken();
+ Token tok2 = tok1;
+ tok2.type = Token::Identifier;
+ tok2.range.endOffset = tok2.range.startOffset;
+ tok2.value = InternalValue();
+ return MakeParseErrorTL(ErrorCode::ExpectedToken, tok1, tok2, Token::In, ',');
+ }
+
+ auto pivotToken = lexer.PeekNextToken();
+ ExpressionParser exprPraser(m_settings);
+ auto valueExpr = exprPraser.ParseFullExpression(lexer, false);
+ if (!valueExpr)
+ return valueExpr.get_unexpected();
+ // return MakeParseError(ErrorCode::ExpectedExpression, pivotToken);
+
+ Token flagsTok;
+ bool isRecursive = false;
+ if (lexer.EatIfEqual(Keyword::Recursive, &flagsTok))
+ {
+ isRecursive = true;
+ }
+
+ ExpressionEvaluatorPtr<> ifExpr;
+ if (lexer.EatIfEqual(Keyword::If))
+ {
+ auto parsedExpr = exprPraser.ParseFullExpression(lexer, false);
+ if (!parsedExpr)
+ return parsedExpr.get_unexpected();
+ ifExpr = *parsedExpr;
+ }
+ else if (lexer.PeekNextToken() != Token::Eof)
+ {
+ auto tok1 = lexer.PeekNextToken();
+ return MakeParseErrorTL(ErrorCode::ExpectedToken, tok1, Token::If, Token::Recursive, Token::Eof);
+ }
+
+ auto renderer = std::make_shared<ForStatement>(vars, *valueExpr, ifExpr, isRecursive);
+ StatementInfo statementInfo = StatementInfo::Create(StatementInfo::ForStatement, stmtTok);
+ statementInfo.renderer = renderer;
+ statementsInfo.push_back(statementInfo);
+ return ParseResult();
+}
+
+StatementsParser::ParseResult StatementsParser::ParseEndFor(LexScanner&, StatementInfoList& statementsInfo, const Token& stmtTok)
+{
+ if (statementsInfo.size() <= 1)
+ return MakeParseError(ErrorCode::UnexpectedStatement, stmtTok);
+
+ StatementInfo info = statementsInfo.back();
+ RendererPtr elseRenderer;
+ if (info.type == StatementInfo::ElseIfStatement)
+ {
+ auto r = std::static_pointer_cast<ElseBranchStatement>(info.renderer);
+ r->SetMainBody(info.compositions[0]);
+ elseRenderer = std::static_pointer_cast<IRendererBase>(r);
+
+ statementsInfo.pop_back();
+ info = statementsInfo.back();
+ }
+
+ if (info.type != StatementInfo::ForStatement)
+ {
+ return MakeParseError(ErrorCode::UnexpectedStatement, stmtTok);
+ }
+
+ statementsInfo.pop_back();
+ auto renderer = static_cast<ForStatement*>(info.renderer.get());
+ renderer->SetMainBody(info.compositions[0]);
+ if (elseRenderer)
+ renderer->SetElseBody(elseRenderer);
+
+ statementsInfo.back().currentComposition->AddRenderer(info.renderer);
+
+ return ParseResult();
+}
+
+StatementsParser::ParseResult StatementsParser::ParseIf(LexScanner &lexer, StatementInfoList &statementsInfo,
+ const Token &stmtTok)
+{
+ auto pivotTok = lexer.PeekNextToken();
+ ExpressionParser exprParser(m_settings);
+ auto valueExpr = exprParser.ParseFullExpression(lexer);
+ if (!valueExpr)
+ return MakeParseError(ErrorCode::ExpectedExpression, pivotTok);
+
+ auto renderer = std::make_shared<IfStatement>(*valueExpr);
+ StatementInfo statementInfo = StatementInfo::Create(StatementInfo::IfStatement, stmtTok);
+ statementInfo.renderer = renderer;
+ statementsInfo.push_back(statementInfo);
+ return ParseResult();
+}
+
+StatementsParser::ParseResult StatementsParser::ParseElse(LexScanner& /*lexer*/, StatementInfoList& statementsInfo
+ , const Token& stmtTok)
+{
+ auto renderer = std::make_shared<ElseBranchStatement>(ExpressionEvaluatorPtr<>());
+ StatementInfo statementInfo = StatementInfo::Create(StatementInfo::ElseIfStatement, stmtTok);
+ statementInfo.renderer = std::static_pointer_cast<IRendererBase>(renderer);
+ statementsInfo.push_back(statementInfo);
+ return ParseResult();
+}
+
+StatementsParser::ParseResult StatementsParser::ParseElIf(LexScanner& lexer, StatementInfoList& statementsInfo
+ , const Token& stmtTok)
+{
+ auto pivotTok = lexer.PeekNextToken();
+ ExpressionParser exprParser(m_settings);
+ auto valueExpr = exprParser.ParseFullExpression(lexer);
+ if (!valueExpr)
+ return MakeParseError(ErrorCode::ExpectedExpression, pivotTok);
+
+ auto renderer = std::make_shared<ElseBranchStatement>(*valueExpr);
+ StatementInfo statementInfo = StatementInfo::Create(StatementInfo::ElseIfStatement, stmtTok);
+ statementInfo.renderer = std::static_pointer_cast<IRendererBase>(renderer);
+ statementsInfo.push_back(statementInfo);
+ return ParseResult();
+}
+
+StatementsParser::ParseResult StatementsParser::ParseEndIf(LexScanner&, StatementInfoList& statementsInfo, const Token& stmtTok)
+{
+ if (statementsInfo.size() <= 1)
+ return MakeParseError(ErrorCode::UnexpectedStatement, stmtTok);
+
+ auto info = statementsInfo.back();
+ statementsInfo.pop_back();
+
+ std::list<StatementPtr<ElseBranchStatement>> elseBranches;
+
+ auto errorTok = stmtTok;
+ while (info.type != StatementInfo::IfStatement)
+ {
+ if (info.type != StatementInfo::ElseIfStatement)
+ return MakeParseError(ErrorCode::UnexpectedStatement, errorTok);
+
+ auto elseRenderer = std::static_pointer_cast<ElseBranchStatement>(info.renderer);
+ elseRenderer->SetMainBody(info.compositions[0]);
+
+ elseBranches.push_front(elseRenderer);
+ errorTok = info.token;
+ info = statementsInfo.back();
+ statementsInfo.pop_back();
+ }
+
+ auto renderer = static_cast<IfStatement*>(info.renderer.get());
+ renderer->SetMainBody(info.compositions[0]);
+
+ for (auto& b : elseBranches)
+ renderer->AddElseBranch(b);
+
+ statementsInfo.back().currentComposition->AddRenderer(info.renderer);
+
+ return ParseResult();
+}
+
+StatementsParser::ParseResult StatementsParser::ParseSet(LexScanner& lexer, StatementInfoList& statementsInfo, const Token& stmtTok)
+{
+ std::vector<std::string> vars;
+
+ while (lexer.PeekNextToken() == Token::Identifier)
+ {
+ auto tok = lexer.NextToken();
+ vars.push_back(AsString(tok.value));
+ if (lexer.NextToken() != ',')
+ {
+ lexer.ReturnToken();
+ break;
+ }
+ }
+
+ if (vars.empty())
+ return MakeParseError(ErrorCode::ExpectedIdentifier, lexer.PeekNextToken());
+
+ ExpressionParser exprParser(m_settings);
+ if (lexer.EatIfEqual('='))
+ {
+ const auto expr = exprParser.ParseFullExpression(lexer);
+ if (!expr)
+ return expr.get_unexpected();
+ statementsInfo.back().currentComposition->AddRenderer(
+ std::make_shared<SetLineStatement>(std::move(vars), *expr));
+ }
+ else if (lexer.EatIfEqual('|'))
+ {
+ const auto expr = exprParser.ParseFilterExpression(lexer);
+ if (!expr)
+ return expr.get_unexpected();
+ auto statementInfo = StatementInfo::Create(
+ StatementInfo::SetStatement, stmtTok);
+ statementInfo.renderer = std::make_shared<SetFilteredBlockStatement>(
+ std::move(vars), *expr);
+ statementsInfo.push_back(std::move(statementInfo));
+ }
+ else
+ {
+ auto operTok = lexer.NextToken();
+ if (lexer.NextToken() != Token::Eof)
+ return MakeParseError(ErrorCode::YetUnsupported, operTok, {std::move(stmtTok)});
+ auto statementInfo = StatementInfo::Create(
+ StatementInfo::SetStatement, stmtTok);
+ statementInfo.renderer = std::make_shared<SetRawBlockStatement>(
+ std::move(vars));
+ statementsInfo.push_back(std::move(statementInfo));
+ }
+
+ return {};
+}
+
+StatementsParser::ParseResult StatementsParser::ParseEndSet(LexScanner&
+ , StatementInfoList& statementsInfo
+ , const Token& stmtTok)
+{
+ if (statementsInfo.size() <= 1)
+ return MakeParseError(ErrorCode::UnexpectedStatement, stmtTok);
+
+ const auto info = statementsInfo.back();
+ if (info.type != StatementInfo::SetStatement)
+ return MakeParseError(ErrorCode::UnexpectedStatement, stmtTok);
+
+ auto &renderer = *boost::polymorphic_downcast<SetBlockStatement*>(
+ info.renderer.get());
+ renderer.SetBody(info.compositions[0]);
+
+ statementsInfo.pop_back();
+ statementsInfo.back().currentComposition->AddRenderer(info.renderer);
+
+ return {};
+}
+
+StatementsParser::ParseResult StatementsParser::ParseBlock(LexScanner& lexer, StatementInfoList& statementsInfo
+ , const Token& stmtTok)
+{
+ if (statementsInfo.empty())
+ return MakeParseError(ErrorCode::UnexpectedStatement, stmtTok);
+
+ Token nextTok = lexer.NextToken();
+ if (nextTok != Token::Identifier)
+ return MakeParseError(ErrorCode::ExpectedIdentifier, nextTok);
+
+ std::string blockName = AsString(nextTok.value);
+
+ auto& info = statementsInfo.back();
+ RendererPtr blockRenderer;
+ StatementInfo::Type blockType = StatementInfo::ParentBlockStatement;
+ if (info.type == StatementInfo::ExtendsStatement)
+ {
+ blockRenderer = std::make_shared<BlockStatement>(blockName);
+ blockType = StatementInfo::BlockStatement;
+ }
+ else
+ {
+ bool isScoped = false;
+ if (lexer.EatIfEqual(Keyword::Scoped, &nextTok))
+ isScoped = true;
+ else
+ {
+ nextTok = lexer.PeekNextToken();
+ if (nextTok != Token::Eof)
+ return MakeParseErrorTL(ErrorCode::ExpectedToken, nextTok, Token::Scoped);
+ }
+
+ blockRenderer = std::make_shared<ParentBlockStatement>(blockName, isScoped);
+ }
+
+ StatementInfo statementInfo = StatementInfo::Create(blockType, stmtTok);
+ statementInfo.renderer = std::move(blockRenderer);
+ statementsInfo.push_back(statementInfo);
+ return ParseResult();
+}
+
+StatementsParser::ParseResult StatementsParser::ParseEndBlock(LexScanner& lexer, StatementInfoList& statementsInfo, const Token& stmtTok)
+{
+ if (statementsInfo.size() <= 1)
+ return MakeParseError(ErrorCode::UnexpectedStatement, stmtTok);
+
+ Token nextTok = lexer.PeekNextToken();
+ if (nextTok != Token::Identifier && nextTok != Token::Eof)
+ {
+ Token tok2;
+ tok2.type = Token::Identifier;
+ Token tok3;
+ tok3.type = Token::Eof;
+ return MakeParseError(ErrorCode::ExpectedToken, nextTok, {tok2, tok3});
+ }
+
+ if (nextTok == Token::Identifier)
+ lexer.EatToken();
+
+ auto info = statementsInfo.back();
+ statementsInfo.pop_back();
+
+ if (info.type == StatementInfo::BlockStatement)
+ {
+ auto blockStmt = std::static_pointer_cast<BlockStatement>(info.renderer);
+ blockStmt->SetMainBody(info.compositions[0]);
+ auto& extendsInfo = statementsInfo.back();
+ auto extendsStmt = std::static_pointer_cast<ExtendsStatement>(extendsInfo.renderer);
+ extendsStmt->AddBlock(std::static_pointer_cast<BlockStatement>(info.renderer));
+ }
+ else if (info.type == StatementInfo::ParentBlockStatement)
+ {
+ auto blockStmt = std::static_pointer_cast<ParentBlockStatement>(info.renderer);
+ blockStmt->SetMainBody(info.compositions[0]);
+ statementsInfo.back().currentComposition->AddRenderer(info.renderer);
+ }
+ else
+ {
+ return MakeParseError(ErrorCode::UnexpectedStatement, stmtTok);
+ }
+
+ return ParseResult();
+}
+
+StatementsParser::ParseResult StatementsParser::ParseExtends(LexScanner& lexer, StatementInfoList& statementsInfo, const Token& stmtTok)
+{
+ if (statementsInfo.empty())
+ return MakeParseError(ErrorCode::UnexpectedStatement, stmtTok);
+
+ if (!m_env)
+ return MakeParseError(ErrorCode::TemplateEnvAbsent, stmtTok);
+
+ Token tok = lexer.NextToken();
+ if (tok != Token::String && tok != Token::Identifier)
+ {
+ auto tok2 = tok;
+ tok2.type = Token::Identifier;
+ tok2.range.endOffset = tok2.range.startOffset;
+ tok2.value = EmptyValue{};
+ return MakeParseErrorTL(ErrorCode::ExpectedToken, tok, tok2, Token::String);
+ }
+
+ auto renderer = std::make_shared<ExtendsStatement>(AsString(tok.value), tok == Token::String);
+ statementsInfo.back().currentComposition->AddRenderer(renderer);
+
+ StatementInfo statementInfo = StatementInfo::Create(StatementInfo::ExtendsStatement, stmtTok);
+ statementInfo.renderer = renderer;
+ statementsInfo.push_back(statementInfo);
+
+ return ParseResult();
+}
+
+StatementsParser::ParseResult StatementsParser::ParseMacro(LexScanner& lexer, StatementInfoList& statementsInfo, const Token& stmtTok)
+{
+ if (statementsInfo.empty())
+ return MakeParseError(ErrorCode::UnexpectedStatement, stmtTok);
+
+ Token nextTok = lexer.NextToken();
+ if (nextTok != Token::Identifier)
+ return MakeParseError(ErrorCode::ExpectedIdentifier, nextTok);
+
+ std::string macroName = AsString(nextTok.value);
+ MacroParams macroParams;
+
+ if (lexer.EatIfEqual('('))
+ {
+ auto result = ParseMacroParams(lexer);
+ if (!result)
+ return result.get_unexpected();
+
+ macroParams = std::move(result.value());
+ }
+ else if (lexer.PeekNextToken() != Token::Eof)
+ {
+ Token tok = lexer.PeekNextToken();
+
+ return MakeParseErrorTL(ErrorCode::UnexpectedToken, tok, Token::RBracket, Token::Eof);
+ }
+
+ auto renderer = std::make_shared<MacroStatement>(std::move(macroName), std::move(macroParams));
+ StatementInfo statementInfo = StatementInfo::Create(StatementInfo::MacroStatement, stmtTok);
+ statementInfo.renderer = renderer;
+ statementsInfo.push_back(statementInfo);
+
+ return ParseResult();
+}
+
+nonstd::expected<MacroParams, ParseError> StatementsParser::ParseMacroParams(LexScanner& lexer)
+{
+ MacroParams items;
+
+ if (lexer.EatIfEqual(')'))
+ return std::move(items);
+
+ ExpressionParser exprParser(m_settings);
+
+ do
+ {
+ Token name = lexer.NextToken();
+ if (name != Token::Identifier)
+ return MakeParseError(ErrorCode::ExpectedIdentifier, name);
+
+ ExpressionEvaluatorPtr<> defVal;
+ if (lexer.EatIfEqual('='))
+ {
+ auto result = exprParser.ParseFullExpression(lexer, false);
+ if (!result)
+ return result.get_unexpected();
+
+ defVal = *result;
+ }
+
+ MacroParam p;
+ p.paramName = AsString(name.value);
+ p.defaultValue = std::move(defVal);
+ items.push_back(std::move(p));
+
+ } while (lexer.EatIfEqual(','));
+
+ auto tok = lexer.NextToken();
+ if (tok != ')')
+ return MakeParseError(ErrorCode::ExpectedRoundBracket, tok);
+
+ return std::move(items);
+}
+
+StatementsParser::ParseResult StatementsParser::ParseEndMacro(LexScanner&, StatementInfoList& statementsInfo, const Token& stmtTok)
+{
+ if (statementsInfo.size() <= 1)
+ return MakeParseError(ErrorCode::UnexpectedStatement, stmtTok);
+
+ StatementInfo info = statementsInfo.back();
+
+ if (info.type != StatementInfo::MacroStatement)
+ {
+ return MakeParseError(ErrorCode::UnexpectedStatement, stmtTok);
+ }
+
+ statementsInfo.pop_back();
+ auto renderer = static_cast<MacroStatement*>(info.renderer.get());
+ renderer->SetMainBody(info.compositions[0]);
+
+ statementsInfo.back().currentComposition->AddRenderer(info.renderer);
+
+ return ParseResult();
+}
+
+StatementsParser::ParseResult StatementsParser::ParseCall(LexScanner& lexer, StatementInfoList& statementsInfo, const Token& stmtTok)
+{
+ if (statementsInfo.empty())
+ return MakeParseError(ErrorCode::UnexpectedStatement, stmtTok);
+
+ MacroParams callbackParams;
+
+ if (lexer.EatIfEqual('('))
+ {
+ auto result = ParseMacroParams(lexer);
+ if (!result)
+ return result.get_unexpected();
+
+ callbackParams = std::move(result.value());
+ }
+
+ Token nextTok = lexer.NextToken();
+ if (nextTok != Token::Identifier)
+ {
+ Token tok = nextTok;
+ Token tok1;
+ tok1.type = Token::Identifier;
+
+ return MakeParseError(ErrorCode::UnexpectedToken, tok, {tok1});
+ }
+
+ std::string macroName = AsString(nextTok.value);
+
+ CallParamsInfo callParams;
+ if (lexer.EatIfEqual('('))
+ {
+ ExpressionParser exprParser(m_settings);
+ auto result = exprParser.ParseCallParams(lexer);
+ if (!result)
+ return result.get_unexpected();
+
+ callParams = std::move(result.value());
+ }
+
+ auto renderer = std::make_shared<MacroCallStatement>(std::move(macroName), std::move(callParams), std::move(callbackParams));
+ StatementInfo statementInfo = StatementInfo::Create(StatementInfo::MacroCallStatement, stmtTok);
+ statementInfo.renderer = renderer;
+ statementsInfo.push_back(statementInfo);
+
+ return ParseResult();
+}
+
+StatementsParser::ParseResult StatementsParser::ParseEndCall(LexScanner&, StatementInfoList& statementsInfo, const Token& stmtTok)
+{
+ if (statementsInfo.size() <= 1)
+ return MakeParseError(ErrorCode::UnexpectedStatement, stmtTok);
+
+ StatementInfo info = statementsInfo.back();
+
+ if (info.type != StatementInfo::MacroCallStatement)
+ {
+ return MakeParseError(ErrorCode::UnexpectedStatement, stmtTok);
+ }
+
+ statementsInfo.pop_back();
+ auto renderer = static_cast<MacroCallStatement*>(info.renderer.get());
+ renderer->SetMainBody(info.compositions[0]);
+
+ statementsInfo.back().currentComposition->AddRenderer(info.renderer);
+
+ return ParseResult();
+}
+
+StatementsParser::ParseResult StatementsParser::ParseInclude(LexScanner& lexer, StatementInfoList& statementsInfo, const Token& stmtTok)
+{
+ if (statementsInfo.empty())
+ return MakeParseError(ErrorCode::UnexpectedStatement, stmtTok);
+
+ // auto operTok = lexer.NextToken();
+ ExpressionEvaluatorPtr<> valueExpr;
+ ExpressionParser exprParser(m_settings);
+ auto expr = exprParser.ParseFullExpression(lexer);
+ if (!expr)
+ return expr.get_unexpected();
+ valueExpr = *expr;
+
+ Token nextTok = lexer.PeekNextToken();
+ bool isIgnoreMissing = false;
+ bool isWithContext = true;
+ bool hasIgnoreMissing = false;
+ if (lexer.EatIfEqual(Keyword::Ignore))
+ {
+ if (lexer.EatIfEqual(Keyword::Missing))
+ isIgnoreMissing = true;
+ else
+ return MakeParseErrorTL(ErrorCode::ExpectedToken, lexer.PeekNextToken(), Token::Missing);
+
+ hasIgnoreMissing = true;
+ nextTok = lexer.PeekNextToken();
+ }
+
+ auto kw = lexer.GetAsKeyword(nextTok);
+ bool hasContextControl = false;
+ if (kw == Keyword::With || kw == Keyword::Without)
+ {
+ lexer.EatToken();
+ isWithContext = kw == Keyword::With;
+ if (!lexer.EatIfEqual(Keyword::Context))
+ return MakeParseErrorTL(ErrorCode::ExpectedToken, lexer.PeekNextToken(), Token::Context);
+
+ nextTok = lexer.PeekNextToken();
+ hasContextControl = true;
+ }
+
+ if (nextTok != Token::Eof)
+ {
+ if (hasContextControl)
+ return MakeParseErrorTL(ErrorCode::ExpectedEndOfStatement, nextTok, Token::Eof);
+
+ if (hasIgnoreMissing)
+ return MakeParseErrorTL(ErrorCode::UnexpectedToken, nextTok, Token::Eof, Token::With, Token::Without);
+
+ return MakeParseErrorTL(ErrorCode::UnexpectedToken, nextTok, Token::Eof, Token::Ignore, Token::With, Token::Without);
+ }
+
+ if (!m_env && !isIgnoreMissing)
+ return MakeParseError(ErrorCode::TemplateEnvAbsent, stmtTok);
+
+ auto renderer = std::make_shared<IncludeStatement>(isIgnoreMissing, isWithContext);
+ renderer->SetIncludeNamesExpr(valueExpr);
+ statementsInfo.back().currentComposition->AddRenderer(renderer);
+
+ return ParseResult();
+}
+
+StatementsParser::ParseResult StatementsParser::ParseImport(LexScanner& lexer, StatementInfoList& statementsInfo, const Token& stmtTok)
+{
+ if (!m_env)
+ return MakeParseError(ErrorCode::TemplateEnvAbsent, stmtTok);
+
+ ExpressionEvaluatorPtr<> valueExpr;
+ ExpressionParser exprParser(m_settings);
+ auto expr = exprParser.ParseFullExpression(lexer);
+ if (!expr)
+ return expr.get_unexpected();
+ valueExpr = *expr;
+
+ if (!lexer.EatIfEqual(Keyword::As))
+ return MakeParseErrorTL(ErrorCode::ExpectedToken, lexer.PeekNextToken(), Token::As);
+
+ Token name;
+ if (!lexer.EatIfEqual(Token::Identifier, &name))
+ return MakeParseErrorTL(ErrorCode::ExpectedToken, lexer.PeekNextToken(), Token::Identifier);
+
+ Token nextTok = lexer.PeekNextToken();
+ auto kw = lexer.GetAsKeyword(nextTok);
+ bool hasContextControl = false;
+ bool isWithContext = false;
+ if (kw == Keyword::With || kw == Keyword::Without)
+ {
+ lexer.EatToken();
+ isWithContext = kw == Keyword::With;
+ if (!lexer.EatIfEqual(Keyword::Context))
+ return MakeParseErrorTL(ErrorCode::ExpectedToken, lexer.PeekNextToken(), Token::Context);
+
+ nextTok = lexer.PeekNextToken();
+ hasContextControl = true;
+ }
+
+ if (nextTok != Token::Eof)
+ {
+ if (hasContextControl)
+ return MakeParseErrorTL(ErrorCode::ExpectedEndOfStatement, nextTok, Token::Eof);
+
+ return MakeParseErrorTL(ErrorCode::UnexpectedToken, nextTok, Token::Eof, Token::With, Token::Without);
+ }
+
+ auto renderer = std::make_shared<ImportStatement>(isWithContext);
+ renderer->SetImportNameExpr(valueExpr);
+ renderer->SetNamespace(AsString(name.value));
+ statementsInfo.back().currentComposition->AddRenderer(renderer);
+
+ return ParseResult();
+}
+
+StatementsParser::ParseResult StatementsParser::ParseFrom(LexScanner& lexer, StatementInfoList& statementsInfo, const Token& stmtTok)
+{
+ if (!m_env)
+ return MakeParseError(ErrorCode::TemplateEnvAbsent, stmtTok);
+
+ ExpressionEvaluatorPtr<> valueExpr;
+ ExpressionParser exprParser(m_settings);
+ auto expr = exprParser.ParseFullExpression(lexer);
+ if (!expr)
+ return expr.get_unexpected();
+ valueExpr = *expr;
+
+ if (!lexer.EatIfEqual(Keyword::Import))
+ return MakeParseErrorTL(ErrorCode::ExpectedToken, lexer.PeekNextToken(), Token::Identifier);
+
+ std::vector<std::pair<std::string, std::string>> mappedNames;
+
+ Token nextTok;
+ bool hasContextControl = false;
+ bool isWithContext = false;
+
+ for (;;)
+ {
+ bool hasComma = false;
+ if (!mappedNames.empty())
+ {
+ if (!lexer.EatIfEqual(Token::Comma))
+ hasComma = true;;
+ }
+
+ nextTok = lexer.PeekNextToken();
+ auto kw = lexer.GetAsKeyword(nextTok);
+ if (kw == Keyword::With || kw == Keyword::Without)
+ {
+ lexer.NextToken();
+ if (lexer.EatIfEqual(Keyword::Context))
+ {
+ hasContextControl = true;
+ isWithContext = kw == Keyword::With;
+ nextTok = lexer.PeekNextToken();
+ break;
+ }
+ else
+ {
+ lexer.ReturnToken();
+ }
+ }
+
+ if (hasComma)
+ break;
+
+ std::pair<std::string, std::string> macroMap;
+ if (!lexer.EatIfEqual(Token::Identifier, &nextTok))
+ return MakeParseErrorTL(ErrorCode::ExpectedToken, nextTok, Token::Identifier);
+
+ macroMap.first = AsString(nextTok.value);
+
+ if (lexer.EatIfEqual(Keyword::As))
+ {
+ if (!lexer.EatIfEqual(Token::Identifier, &nextTok))
+ return MakeParseErrorTL(ErrorCode::ExpectedToken, nextTok, Token::Identifier);
+ macroMap.second = AsString(nextTok.value);
+ }
+ else
+ {
+ macroMap.second = macroMap.first;
+ }
+ mappedNames.push_back(std::move(macroMap));
+ }
+
+ if (nextTok != Token::Eof)
+ {
+ if (hasContextControl)
+ return MakeParseErrorTL(ErrorCode::ExpectedEndOfStatement, nextTok, Token::Eof);
+
+ if (mappedNames.empty())
+ MakeParseErrorTL(ErrorCode::UnexpectedToken, nextTok, Token::Eof, Token::Identifier);
+ else
+ MakeParseErrorTL(ErrorCode::UnexpectedToken, nextTok, Token::Eof, Token::Comma, Token::With, Token::Without);
+ }
+
+ auto renderer = std::make_shared<ImportStatement>(isWithContext);
+ renderer->SetImportNameExpr(valueExpr);
+
+ for (auto& nameInfo : mappedNames)
+ renderer->AddNameToImport(std::move(nameInfo.first), std::move(nameInfo.second));
+
+ statementsInfo.back().currentComposition->AddRenderer(renderer);
+
+ return ParseResult();
+}
+
+StatementsParser::ParseResult StatementsParser::ParseDo(LexScanner& lexer, StatementInfoList& statementsInfo, const Token& /*stmtTok*/)
+{
+ ExpressionEvaluatorPtr<> valueExpr;
+ ExpressionParser exprParser(m_settings);
+ auto expr = exprParser.ParseFullExpression(lexer);
+ if (!expr)
+ return expr.get_unexpected();
+ valueExpr = *expr;
+
+ auto renderer = std::make_shared<DoStatement>(valueExpr);
+ statementsInfo.back().currentComposition->AddRenderer(renderer);
+
+ return jinja2::StatementsParser::ParseResult();
+}
+
+StatementsParser::ParseResult StatementsParser::ParseWith(LexScanner& lexer, StatementInfoList& statementsInfo, const Token& stmtTok)
+{
+ std::vector<std::pair<std::string, ExpressionEvaluatorPtr<>>> vars;
+
+ ExpressionParser exprParser(m_settings);
+ while (lexer.PeekNextToken() == Token::Identifier)
+ {
+ auto nameTok = lexer.NextToken();
+ if (!lexer.EatIfEqual('='))
+ return MakeParseErrorTL(ErrorCode::ExpectedToken, lexer.PeekNextToken(), '=');
+
+ auto expr = exprParser.ParseFullExpression(lexer);
+ if (!expr)
+ return expr.get_unexpected();
+ auto valueExpr = *expr;
+
+ vars.emplace_back(AsString(nameTok.value), valueExpr);
+
+ if (!lexer.EatIfEqual(','))
+ break;
+ }
+
+ auto nextTok = lexer.PeekNextToken();
+ if (vars.empty())
+ return MakeParseError(ErrorCode::ExpectedIdentifier, nextTok);
+
+ if (nextTok != Token::Eof)
+ return MakeParseErrorTL(ErrorCode::ExpectedToken, nextTok, Token::Eof, ',');
+
+ auto renderer = std::make_shared<WithStatement>();
+ renderer->SetScopeVars(std::move(vars));
+ StatementInfo statementInfo = StatementInfo::Create(StatementInfo::WithStatement, stmtTok);
+ statementInfo.renderer = renderer;
+ statementsInfo.push_back(statementInfo);
+
+ return ParseResult();
+}
+
+StatementsParser::ParseResult StatementsParser::ParseEndWith(LexScanner& /*lexer*/, StatementInfoList& statementsInfo, const Token& stmtTok)
+{
+ if (statementsInfo.size() <= 1)
+ return MakeParseError(ErrorCode::UnexpectedStatement, stmtTok);
+
+ StatementInfo info = statementsInfo.back();
+
+ if (info.type != StatementInfo::WithStatement)
+ {
+ return MakeParseError(ErrorCode::UnexpectedStatement, stmtTok);
+ }
+
+ statementsInfo.pop_back();
+ auto renderer = static_cast<WithStatement*>(info.renderer.get());
+ renderer->SetMainBody(info.compositions[0]);
+
+ statementsInfo.back().currentComposition->AddRenderer(info.renderer);
+
+ return ParseResult();
+}
+
+StatementsParser::ParseResult StatementsParser::ParseFilter(LexScanner& lexer, StatementInfoList& statementsInfo, const Token& stmtTok)
+{
+ ExpressionParser exprParser(m_settings);
+ auto filterExpr = exprParser.ParseFilterExpression(lexer);
+ if (!filterExpr)
+ {
+ return filterExpr.get_unexpected();
+ }
+
+ auto renderer = std::make_shared<FilterStatement>(*filterExpr);
+ auto statementInfo = StatementInfo::Create(
+ StatementInfo::FilterStatement, stmtTok);
+ statementInfo.renderer = std::move(renderer);
+ statementsInfo.push_back(std::move(statementInfo));
+
+ return {};
+}
+
+StatementsParser::ParseResult StatementsParser::ParseEndFilter(LexScanner&, StatementInfoList& statementsInfo, const Token& stmtTok)
+{
+ if (statementsInfo.size() <= 1)
+ return MakeParseError(ErrorCode::UnexpectedStatement, stmtTok);
+
+ const auto info = statementsInfo.back();
+ if (info.type != StatementInfo::FilterStatement)
+ {
+ return MakeParseError(ErrorCode::UnexpectedStatement, stmtTok);
+ }
+
+ statementsInfo.pop_back();
+ auto &renderer = *boost::polymorphic_downcast<FilterStatement*>(info.renderer.get());
+ renderer.SetBody(info.compositions[0]);
+
+ statementsInfo.back().currentComposition->AddRenderer(info.renderer);
+
+ return {};
+}
+
+} // namespace jinja2
diff --git a/contrib/libs/jinja2cpp/src/template_parser.h b/contrib/libs/jinja2cpp/src/template_parser.h
new file mode 100644
index 0000000000..89c4270758
--- /dev/null
+++ b/contrib/libs/jinja2cpp/src/template_parser.h
@@ -0,0 +1,1081 @@
+#ifndef JINJA2CPP_SRC_TEMPLATE_PARSER_H
+#define JINJA2CPP_SRC_TEMPLATE_PARSER_H
+
+#include "error_handling.h"
+#include "expression_parser.h"
+#include "helpers.h"
+#include "lexer.h"
+#include "lexertk.h"
+#include "renderer.h"
+#include "statements.h"
+#include "template_parser.h"
+#include "value_visitors.h"
+
+#include <boost/algorithm/string/classification.hpp>
+#include <jinja2cpp/error_info.h>
+#include <jinja2cpp/template_env.h>
+#include <contrib/restricted/expected-lite/include/nonstd/expected.hpp>
+
+#include <list>
+#include <sstream>
+#include <string>
+#include <vector>
+
+#ifdef JINJA2CPP_USE_REGEX_BOOST
+#include <boost/regex.hpp>
+template <typename CharType>
+using BasicRegex = boost::basic_regex<CharType>;
+using Regex = boost::regex;
+using WideRegex = boost::wregex;
+template <typename CharIterator>
+using RegexIterator = boost::regex_iterator<CharIterator>;
+#else
+#include <regex>
+template <typename CharType>
+using BasicRegex = std::basic_regex<CharType>;
+using Regex = std::regex;
+using WideRegex = std::wregex;
+template <typename CharIterator>
+using RegexIterator = std::regex_iterator<CharIterator>;
+#endif
+
+namespace jinja2
+{
+template<typename CharT>
+struct ParserTraits;
+
+struct KeywordsInfo
+{
+ MultiStringLiteral name;
+ Keyword type;
+};
+
+struct TokenStrInfo : MultiStringLiteral
+{
+ template<typename CharT>
+ auto GetName() const
+ {
+ return MultiStringLiteral::template GetValue<CharT>();
+ }
+};
+
+template<typename T = void>
+struct ParserTraitsBase
+{
+ static Token::Type s_keywords[];
+ static KeywordsInfo s_keywordsInfo[41];
+ static std::unordered_map<int, MultiStringLiteral> s_tokens;
+ static MultiStringLiteral s_regexp;
+};
+
+template<typename T>
+MultiStringLiteral ParserTraitsBase<T>::s_regexp = UNIVERSAL_STR(
+ R"((\{\{)|(\}\})|(\{%[\+\-]?\s+raw\s+[\+\-]?%\})|(\{%[\+\-]?\s+endraw\s+[\+\-]?%\})|(\{%\s+meta\s+%\})|(\{%\s+endmeta\s+%\})|(\{%)|(%\})|(\{#)|(#\})|(\n))");
+
+template<>
+struct ParserTraits<char> : public ParserTraitsBase<>
+{
+ static Regex GetRoughTokenizer()
+ { return Regex(s_regexp.GetValueStr<char>()); }
+ static Regex GetKeywords()
+ {
+ std::string pattern;
+ std::string prefix("(^");
+ std::string postfix("$)");
+
+ bool isFirst = true;
+ for (auto& info : s_keywordsInfo)
+ {
+ if (!isFirst)
+ pattern += "|";
+ else
+ isFirst = false;
+
+ pattern += prefix + info.name.charValue + postfix;
+ }
+ 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)
+ {
+ char buff[std::max(std::numeric_limits<int64_t>::max_digits10, std::numeric_limits<double>::max_digits10) * 2 + 1];
+ std::copy(str.data() + range.startOffset, str.data() + range.endOffset, buff);
+ buff[range.size()] = 0;
+ InternalValue result;
+ if (hint == Token::IntegerNum)
+ {
+ result = InternalValue(static_cast<int64_t>(strtoll(buff, nullptr, 0)));
+ }
+ else
+ {
+ char* endBuff = nullptr;
+ int64_t val = strtoll(buff, &endBuff, 10);
+ if ((errno == ERANGE) || *endBuff)
+ {
+ endBuff = nullptr;
+ double dblVal = strtod(buff, nullptr);
+ result = static_cast<double>(dblVal);
+ }
+ else
+ result = static_cast<int64_t>(val);
+ }
+ return result;
+ }
+};
+
+template<>
+struct ParserTraits<wchar_t> : public ParserTraitsBase<>
+{
+ static WideRegex GetRoughTokenizer()
+ { return WideRegex(s_regexp.GetValueStr<wchar_t>()); }
+ static WideRegex GetKeywords()
+ {
+ std::wstring pattern;
+ std::wstring prefix(L"(^");
+ std::wstring postfix(L"$)");
+
+ bool isFirst = true;
+ for (auto& info : s_keywordsInfo)
+ {
+ if (!isFirst)
+ pattern += L"|";
+ else
+ isFirst = false;
+
+ pattern += prefix + info.name.wcharValue + postfix;
+ }
+ return WideRegex(pattern);
+ }
+ static std::string GetAsString(const std::wstring& str, CharRange range)
+ {
+ auto srcStr = str.substr(range.startOffset, range.size());
+ return detail::StringConverter<std::wstring, std::string>::DoConvert(srcStr);
+ }
+ static InternalValue RangeToNum(const std::wstring& str, CharRange range, Token::Type hint)
+ {
+ wchar_t buff[std::max(std::numeric_limits<int64_t>::max_digits10, std::numeric_limits<double>::max_digits10) * 2 + 1];
+ std::copy(str.data() + range.startOffset, str.data() + range.endOffset, buff);
+ buff[range.size()] = 0;
+ InternalValue result;
+ if (hint == Token::IntegerNum)
+ {
+ result = static_cast<int64_t>(wcstoll(buff, nullptr, 0));
+ }
+ else
+ {
+ wchar_t* endBuff = nullptr;
+ int64_t val = wcstoll(buff, &endBuff, 10);
+ if ((errno == ERANGE) || *endBuff)
+ {
+ endBuff = nullptr;
+ double dblVal = wcstod(buff, nullptr);
+ result = static_cast<double>(dblVal);
+ }
+ else
+ result = static_cast<int64_t>(val);
+ }
+ return result;
+ }
+};
+
+struct StatementInfo
+{
+ enum Type {
+ TemplateRoot,
+ IfStatement,
+ ElseIfStatement,
+ ForStatement,
+ SetStatement,
+ ExtendsStatement,
+ BlockStatement,
+ ParentBlockStatement,
+ MacroStatement,
+ MacroCallStatement,
+ WithStatement,
+ FilterStatement
+ };
+
+ using ComposedPtr = std::shared_ptr<ComposedRenderer>;
+ Type type;
+ ComposedPtr currentComposition;
+ std::vector<ComposedPtr> compositions;
+ Token token;
+ RendererPtr renderer;
+
+ static StatementInfo Create(Type type, const Token& tok, ComposedPtr renderers = std::make_shared<ComposedRenderer>())
+ {
+ StatementInfo result;
+ result.type = type;
+ result.currentComposition = renderers;
+ result.compositions.push_back(renderers);
+ result.token = tok;
+ return result;
+ }
+};
+
+using StatementInfoList = std::list<StatementInfo>;
+
+class StatementsParser
+{
+public:
+ using ParseResult = nonstd::expected<void, ParseError>;
+
+ StatementsParser(const Settings& settings, TemplateEnv* env)
+ : m_settings(settings)
+ , m_env(env)
+ {
+ }
+
+ ParseResult Parse(LexScanner& lexer, StatementInfoList& statementsInfo);
+
+private:
+ ParseResult ParseFor(LexScanner& lexer, StatementInfoList& statementsInfo, const Token& stmtTok);
+ ParseResult ParseEndFor(LexScanner& lexer, StatementInfoList& statementsInfo, const Token& stmtTok);
+ ParseResult ParseIf(LexScanner& lexer, StatementInfoList& statementsInfo, const Token& stmtTok);
+ ParseResult ParseElse(LexScanner& lexer, StatementInfoList& statementsInfo, const Token& stmtTok);
+ ParseResult ParseElIf(LexScanner& lexer, StatementInfoList& statementsInfo, const Token& stmtTok);
+ ParseResult ParseEndIf(LexScanner& lexer, StatementInfoList& statementsInfo, const Token& pos);
+ ParseResult ParseSet(LexScanner& lexer, StatementInfoList& statementsInfo, const Token& pos);
+ ParseResult ParseEndSet(LexScanner& lexer, StatementInfoList& statementsInfo, const Token& stmtTok);
+ ParseResult ParseBlock(LexScanner& lexer, StatementInfoList& statementsInfo, const Token& stmtTok);
+ ParseResult ParseEndBlock(LexScanner& lexer, StatementInfoList& statementsInfo, const Token& stmtTok);
+ ParseResult ParseExtends(LexScanner& lexer, StatementInfoList& statementsInfo, const Token& stmtTok);
+ ParseResult ParseMacro(LexScanner& lexer, StatementInfoList& statementsInfo, const Token& stmtTok);
+ nonstd::expected<MacroParams, ParseError> ParseMacroParams(LexScanner& lexer);
+ ParseResult ParseEndMacro(LexScanner& lexer, StatementInfoList& statementsInfo, const Token& stmtTok);
+ ParseResult ParseCall(LexScanner& lexer, StatementInfoList& statementsInfo, const Token& stmtTok);
+ ParseResult ParseEndCall(LexScanner& lexer, StatementInfoList& statementsInfo, const Token& stmtTok);
+ ParseResult ParseInclude(LexScanner& lexer, StatementInfoList& statementsInfo, const Token& stmtTok);
+ ParseResult ParseImport(LexScanner& lexer, StatementInfoList& statementsInfo, const Token& stmtTok);
+ ParseResult ParseFrom(LexScanner& lexer, StatementInfoList& statementsInfo, const Token& stmtTok);
+ ParseResult ParseDo(LexScanner& lexer, StatementInfoList& statementsInfo, const Token& stmtTok);
+ ParseResult ParseWith(LexScanner& lexer, StatementInfoList& statementsInfo, const Token& token);
+ ParseResult ParseEndWith(LexScanner& lexer, StatementInfoList& statementsInfo, const Token& stmtTok);
+ ParseResult ParseFilter(LexScanner& lexer, StatementInfoList& statementsInfo, const Token& stmtTok);
+ ParseResult ParseEndFilter(LexScanner& lexer, StatementInfoList& statementsInfo, const Token& stmtTok);
+
+private:
+ Settings m_settings;
+ TemplateEnv* m_env;
+};
+
+template<typename CharT>
+class TemplateParser : public LexerHelper
+{
+public:
+ using string_t = std::basic_string<CharT>;
+ using traits_t = ParserTraits<CharT>;
+ using sregex_iterator = RegexIterator<typename string_t::const_iterator>;
+ using ErrorInfo = ErrorInfoTpl<CharT>;
+ using ParseResult = nonstd::expected<RendererPtr, std::vector<ErrorInfo>>;
+
+ TemplateParser(const string_t* tpl, const Settings& setts, TemplateEnv* env, std::string tplName)
+ : m_template(tpl)
+ , m_templateName(std::move(tplName))
+ , m_settings(setts)
+ , m_env(env)
+ , m_roughTokenizer(traits_t::GetRoughTokenizer())
+ , m_keywords(traits_t::GetKeywords())
+ , m_metadataType(setts.m_defaultMetadataType)
+ {
+ }
+
+ ParseResult Parse()
+ {
+ auto roughResult = DoRoughParsing();
+
+ if (!roughResult)
+ {
+ return ParseErrorsToErrorInfo(roughResult.error());
+ }
+
+ auto composeRenderer = std::make_shared<ComposedRenderer>();
+
+ auto fineResult = DoFineParsing(composeRenderer);
+ if (!fineResult)
+ return ParseErrorsToErrorInfo(fineResult.error());
+
+ return composeRenderer;
+ }
+
+ MetadataInfo<CharT> GetMetadataInfo() const
+ {
+ MetadataInfo<CharT> result;
+ result.metadataType = m_metadataType;
+ result.metadata = m_metadata;
+ result.location = m_metadataLocation;
+ return result;
+ }
+
+private:
+ enum {
+ RM_Unknown = 0,
+ RM_ExprBegin = 1,
+ RM_ExprEnd,
+ RM_RawBegin,
+ RM_RawEnd,
+ RM_MetaBegin,
+ RM_MetaEnd,
+ RM_StmtBegin,
+ RM_StmtEnd,
+ RM_CommentBegin,
+ RM_CommentEnd,
+ RM_NewLine
+ };
+
+ struct LineInfo
+ {
+ CharRange range;
+ unsigned lineNumber;
+ };
+
+ enum class TextBlockType { RawText, Expression, Statement, Comment, LineStatement, RawBlock, MetaBlock };
+
+ struct TextBlockInfo
+ {
+ CharRange range;
+ TextBlockType type;
+ };
+
+ nonstd::expected<void, std::vector<ParseError>> DoRoughParsing()
+ {
+ std::vector<ParseError> foundErrors;
+
+ auto matchBegin = sregex_iterator(m_template->begin(), m_template->end(), m_roughTokenizer);
+ auto matchEnd = sregex_iterator();
+
+ auto matches = std::distance(matchBegin, matchEnd);
+ // One line, no customization
+ if (matches == 0)
+ {
+ CharRange range{ 0ULL, m_template->size() };
+ m_lines.push_back(LineInfo{ range, 0 });
+ m_textBlocks.push_back(
+ TextBlockInfo{ range, (!m_template->empty() && m_template->front() == '#') ? TextBlockType::LineStatement : TextBlockType::RawText });
+ return nonstd::expected<void, std::vector<ParseError>>();
+ }
+
+ m_currentBlockInfo.range.startOffset = 0;
+ m_currentBlockInfo.range.endOffset = 0;
+ m_currentLineInfo.range = m_currentBlockInfo.range;
+ m_currentLineInfo.lineNumber = 0;
+ if (m_settings.useLineStatements)
+ m_currentBlockInfo.type = m_template->front() == '#' ? TextBlockType::LineStatement : TextBlockType::RawText;
+ else
+ m_currentBlockInfo.type = TextBlockType::RawText;
+ do
+ {
+ auto result = ParseRoughMatch(matchBegin, matchEnd);
+ if (!result)
+ {
+ foundErrors.push_back(result.error());
+ return nonstd::make_unexpected(std::move(foundErrors));
+ }
+ } while (matchBegin != matchEnd);
+ FinishCurrentLine(m_template->size());
+
+ if (m_currentBlockInfo.type == TextBlockType::RawBlock)
+ {
+ nonstd::expected<void, ParseError> result =
+ MakeParseError(ErrorCode::ExpectedRawEnd, MakeToken(Token::RawEnd, { m_template->size(), m_template->size() }));
+ foundErrors.push_back(result.error());
+ return nonstd::make_unexpected(std::move(foundErrors));
+ }
+ else if (m_currentBlockInfo.type == TextBlockType::MetaBlock)
+ {
+ nonstd::expected<void, ParseError> result =
+ MakeParseError(ErrorCode::ExpectedMetaEnd, MakeToken(Token::RawEnd, { m_template->size(), m_template->size() }));
+ foundErrors.push_back(result.error());
+ return nonstd::make_unexpected(std::move(foundErrors));
+ }
+
+ FinishCurrentBlock(m_template->size(), TextBlockType::RawText);
+
+ if (!foundErrors.empty())
+ return nonstd::make_unexpected(std::move(foundErrors));
+ return nonstd::expected<void, std::vector<ParseError>>();
+ }
+ nonstd::expected<void, ParseError> ParseRoughMatch(sregex_iterator& curMatch, const sregex_iterator& /*endMatch*/)
+ {
+ auto match = *curMatch;
+ ++curMatch;
+ unsigned matchType = RM_Unknown;
+ for (unsigned idx = 1; idx != match.size(); ++idx)
+ {
+ if (match.length(idx) != 0)
+ {
+ matchType = idx;
+ break;
+ }
+ }
+
+ size_t matchStart = static_cast<size_t>(match.position());
+
+ switch (matchType)
+ {
+ case RM_NewLine:
+ FinishCurrentLine(match.position());
+ m_currentLineInfo.range.startOffset = m_currentLineInfo.range.endOffset + 1;
+ if (m_currentLineInfo.range.startOffset < m_template->size() &&
+ (m_currentBlockInfo.type == TextBlockType::RawText || m_currentBlockInfo.type == TextBlockType::LineStatement))
+ {
+ if (m_currentBlockInfo.type == TextBlockType::LineStatement)
+ {
+ FinishCurrentBlock(matchStart, TextBlockType::RawText);
+ m_currentBlockInfo.range.startOffset = m_currentLineInfo.range.startOffset;
+ }
+
+ if (m_settings.useLineStatements)
+ m_currentBlockInfo.type =
+ (*m_template)[m_currentLineInfo.range.startOffset] == '#' ? TextBlockType::LineStatement : TextBlockType::RawText;
+ else
+ m_currentBlockInfo.type = TextBlockType::RawText;
+ }
+ break;
+ case RM_CommentBegin:
+ if (m_currentBlockInfo.type == TextBlockType::RawBlock)
+ break;
+ if (m_currentBlockInfo.type != TextBlockType::RawText)
+ {
+ FinishCurrentLine(match.position() + 2);
+ return MakeParseError(ErrorCode::UnexpectedCommentBegin, MakeToken(Token::CommentBegin, { matchStart, matchStart + 2 }));
+ }
+
+ FinishCurrentBlock(matchStart, TextBlockType::Comment);
+ m_currentBlockInfo.range.startOffset = matchStart + 2;
+ m_currentBlockInfo.type = TextBlockType::Comment;
+ break;
+
+ case RM_CommentEnd:
+ if (m_currentBlockInfo.type == TextBlockType::RawBlock)
+ break;
+ if (m_currentBlockInfo.type != TextBlockType::Comment)
+ {
+ 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:
+ StartControlBlock(TextBlockType::Expression, matchStart);
+ break;
+ case RM_ExprEnd:
+ if (m_currentBlockInfo.type == TextBlockType::RawText)
+ {
+ FinishCurrentLine(match.position() + 2);
+ return MakeParseError(ErrorCode::UnexpectedExprEnd, MakeToken(Token::ExprEnd, { matchStart, matchStart + 2 }));
+ }
+ else if (m_currentBlockInfo.type != TextBlockType::Expression || (*m_template)[match.position() - 1] == '\'')
+ break;
+
+ m_currentBlockInfo.range.startOffset = FinishCurrentBlock(matchStart, TextBlockType::RawText);
+ break;
+ case RM_StmtBegin:
+ StartControlBlock(TextBlockType::Statement, matchStart);
+ break;
+ case RM_StmtEnd:
+ if (m_currentBlockInfo.type == TextBlockType::RawText)
+ {
+ FinishCurrentLine(match.position() + 2);
+ return MakeParseError(ErrorCode::UnexpectedStmtEnd, MakeToken(Token::StmtEnd, { matchStart, matchStart + 2 }));
+ }
+ else if (m_currentBlockInfo.type != TextBlockType::Statement || (*m_template)[match.position() - 1] == '\'')
+ break;
+
+ m_currentBlockInfo.range.startOffset = FinishCurrentBlock(matchStart, TextBlockType::RawText);
+ break;
+ case RM_RawBegin:
+ if (m_currentBlockInfo.type == TextBlockType::RawBlock)
+ break;
+ else if (m_currentBlockInfo.type != TextBlockType::RawText && m_currentBlockInfo.type != TextBlockType::Comment)
+ {
+ FinishCurrentLine(match.position() + match.length());
+ return MakeParseError(ErrorCode::UnexpectedRawBegin, MakeToken(Token::RawBegin, { matchStart, matchStart + match.length() }));
+ }
+ StartControlBlock(TextBlockType::RawBlock, matchStart, matchStart + match.length());
+ break;
+ case RM_RawEnd:
+ if (m_currentBlockInfo.type == TextBlockType::Comment)
+ break;
+ else if (m_currentBlockInfo.type != TextBlockType::RawBlock)
+ {
+ FinishCurrentLine(match.position() + match.length());
+ return MakeParseError(ErrorCode::UnexpectedRawEnd, MakeToken(Token::RawEnd, { matchStart, matchStart + match.length() }));
+ }
+ m_currentBlockInfo.range.startOffset = FinishCurrentBlock(matchStart + match.length() - 2, TextBlockType::RawText, matchStart);
+ break;
+ case RM_MetaBegin:
+ if (m_currentBlockInfo.type == TextBlockType::Comment)
+ break;
+ if ((m_currentBlockInfo.type != TextBlockType::RawText && m_currentBlockInfo.type != TextBlockType::Comment) || m_hasMetaBlock)
+ {
+ FinishCurrentLine(match.position() + match.length());
+ return MakeParseError(ErrorCode::UnexpectedMetaBegin, MakeToken(Token::MetaBegin, { matchStart, matchStart + match.length() }));
+ }
+ StartControlBlock(TextBlockType::MetaBlock, matchStart, matchStart + match.length());
+ m_metadataLocation.line = m_currentLineInfo.lineNumber + 1;
+ m_metadataLocation.col = static_cast<unsigned>(match.position() - m_currentLineInfo.range.startOffset + 1);
+ m_metadataLocation.fileName = m_templateName;
+ break;
+ case RM_MetaEnd:
+ if (m_currentBlockInfo.type == TextBlockType::Comment)
+ break;
+ if (m_currentBlockInfo.type != TextBlockType::MetaBlock)
+ {
+ FinishCurrentLine(match.position() + match.length());
+ return MakeParseError(ErrorCode::UnexpectedMetaEnd, MakeToken(Token::MetaEnd, { matchStart, matchStart + match.length() }));
+ }
+ m_currentBlockInfo.range.startOffset = FinishCurrentBlock(matchStart + match.length() - 2, TextBlockType::MetaBlock, matchStart);
+ m_hasMetaBlock = true;
+ break;
+ }
+
+ return nonstd::expected<void, ParseError>();
+ }
+
+ void StartControlBlock(TextBlockType blockType, size_t matchStart, size_t startOffset = 0)
+ {
+ if (!startOffset)
+ startOffset = matchStart + 2;
+
+ size_t endOffset = matchStart;
+ if (m_currentBlockInfo.type != TextBlockType::RawText || m_currentBlockInfo.type == TextBlockType::RawBlock)
+ return;
+ else
+ endOffset = StripBlockLeft(m_currentBlockInfo, startOffset, endOffset, blockType == TextBlockType::Expression ? false : m_settings.lstripBlocks);
+
+ FinishCurrentBlock(endOffset, blockType);
+ if (startOffset < m_template->size() && blockType != TextBlockType::MetaBlock)
+ {
+ if ((*m_template)[startOffset] == '+' || (*m_template)[startOffset] == '-')
+ ++startOffset;
+ }
+
+ m_currentBlockInfo.type = blockType;
+
+ if (blockType == TextBlockType::RawBlock)
+ startOffset = StripBlockRight(m_currentBlockInfo, startOffset - 2, m_settings.trimBlocks);
+
+ m_currentBlockInfo.range.startOffset = startOffset;
+ }
+
+ size_t StripBlockRight(TextBlockInfo& /* currentBlockInfo */, size_t position, bool trimBlocks)
+ {
+ bool doTrim = trimBlocks;
+
+ size_t newPos = position + 2;
+
+ if ((m_currentBlockInfo.type != TextBlockType::RawText) && position != 0)
+ {
+ auto ctrlChar = (*m_template)[position - 1];
+ doTrim = ctrlChar == '-' ? true : (ctrlChar == '+' ? false : doTrim);
+ }
+
+ if (doTrim)
+ {
+ auto locale = std::locale();
+ for (; newPos < m_template->size(); ++newPos)
+ {
+ auto ch = (*m_template)[newPos];
+ if (ch == '\n')
+ {
+ ++newPos;
+ break;
+ }
+ if (!std::isspace(ch, locale))
+ break;
+ }
+ }
+ return newPos;
+ }
+
+ size_t StripBlockLeft(TextBlockInfo& currentBlockInfo, size_t ctrlCharPos, size_t endOffset, bool doStrip)
+ {
+ bool doTotalStrip = false;
+ if (ctrlCharPos < m_template->size())
+ {
+ auto ctrlChar = (*m_template)[ctrlCharPos];
+ if (ctrlChar == '+')
+ doStrip = false;
+ else
+ doTotalStrip = ctrlChar == '-';
+
+ doStrip |= doTotalStrip;
+ }
+ if (!doStrip || (currentBlockInfo.type != TextBlockType::RawText && currentBlockInfo.type != TextBlockType::RawBlock))
+ return endOffset;
+
+ auto locale = std::locale();
+ auto& tpl = *m_template;
+ auto originalOffset = endOffset;
+ bool sameLine = true;
+ for (; endOffset != currentBlockInfo.range.startOffset && endOffset > 0; --endOffset)
+ {
+ auto ch = tpl[endOffset - 1];
+ if (!std::isspace(ch, locale))
+ {
+ if (!sameLine)
+ break;
+
+ return doTotalStrip ? endOffset : originalOffset;
+ }
+ if (ch == '\n')
+ {
+ if (!doTotalStrip)
+ break;
+ sameLine = false;
+ }
+ }
+ return endOffset;
+ }
+
+ nonstd::expected<void, std::vector<ParseError>> DoFineParsing(std::shared_ptr<ComposedRenderer> renderers)
+ {
+ std::vector<ParseError> errors;
+ StatementInfoList statementsStack;
+ StatementInfo root = StatementInfo::Create(StatementInfo::TemplateRoot, Token(), renderers);
+ statementsStack.push_back(root);
+ for (auto& origBlock : m_textBlocks)
+ {
+ auto block = origBlock;
+ if (block.type == TextBlockType::LineStatement)
+ ++block.range.startOffset;
+
+ switch (block.type)
+ {
+ case TextBlockType::RawBlock:
+ case TextBlockType::RawText:
+ {
+ auto range = block.range;
+ if (range.size() == 0)
+ break;
+ auto renderer = std::make_shared<RawTextRenderer>(m_template->data() + range.startOffset, range.size());
+ statementsStack.back().currentComposition->AddRenderer(renderer);
+ break;
+ }
+ case TextBlockType::MetaBlock:
+ {
+ auto range = block.range;
+ if (range.size() == 0)
+ break;
+ auto metadata = std::basic_string_view<CharT>(m_template->data() + range.startOffset, range.size());
+ if (!boost::algorithm::all(metadata, boost::algorithm::is_space()))
+ m_metadata = metadata;
+ break;
+ }
+ case TextBlockType::Expression:
+ {
+ auto parseResult = InvokeParser<RendererPtr, ExpressionParser>(block);
+ if (parseResult)
+ statementsStack.back().currentComposition->AddRenderer(*parseResult);
+ else
+ errors.push_back(parseResult.error());
+ break;
+ }
+ case TextBlockType::Statement:
+ case TextBlockType::LineStatement:
+ {
+ auto parseResult = InvokeParser<void, StatementsParser>(block, statementsStack);
+ if (!parseResult)
+ errors.push_back(parseResult.error());
+ break;
+ }
+ default:
+ break;
+ }
+ }
+
+ if (!errors.empty())
+ return nonstd::make_unexpected(std::move(errors));
+
+ return nonstd::expected<void, std::vector<ParseError>>();
+ }
+ template<typename R, typename P, typename... Args>
+ nonstd::expected<R, ParseError> InvokeParser(const TextBlockInfo& block, Args&&... args)
+ {
+ lexertk::generator<CharT> tokenizer;
+ auto range = block.range;
+ auto start = m_template->data();
+ if (!tokenizer.process(start + range.startOffset, start + range.endOffset))
+ return MakeParseError(ErrorCode::Unspecified, MakeToken(Token::Unknown, { range.startOffset, range.startOffset + 1 }));
+
+ tokenizer.begin();
+ Lexer lexer(
+ [&tokenizer, adjust = range.startOffset]() mutable {
+ lexertk::token tok = tokenizer.next_token();
+ tok.position += adjust;
+ return tok;
+ },
+ this);
+
+ if (!lexer.Preprocess())
+ return MakeParseError(ErrorCode::Unspecified, MakeToken(Token::Unknown, { range.startOffset, range.startOffset + 1 }));
+
+ P praser(m_settings, m_env);
+ LexScanner scanner(lexer);
+ auto result = praser.Parse(scanner, std::forward<Args>(args)...);
+ if (!result)
+ return result.get_unexpected();
+
+ return result;
+ }
+
+ nonstd::unexpected_type<std::vector<ErrorInfo>> ParseErrorsToErrorInfo(const std::vector<ParseError>& errors)
+ {
+ std::vector<ErrorInfo> resultErrors;
+
+ for (auto& e : errors)
+ {
+ typename ErrorInfo::Data errInfoData;
+ errInfoData.code = e.errorCode;
+ errInfoData.srcLoc.fileName = m_templateName;
+ OffsetToLinePos(e.errorToken.range.startOffset, errInfoData.srcLoc.line, errInfoData.srcLoc.col);
+ errInfoData.locationDescr = GetLocationDescr(errInfoData.srcLoc.line, errInfoData.srcLoc.col);
+ errInfoData.extraParams.emplace_back(TokenToString(e.errorToken));
+ for (auto& tok : e.relatedTokens)
+ {
+ errInfoData.extraParams.emplace_back(TokenToString(tok));
+ if (tok.range.startOffset != e.errorToken.range.startOffset)
+ {
+ SourceLocation relLoc;
+ relLoc.fileName = m_templateName;
+ OffsetToLinePos(tok.range.startOffset, relLoc.line, relLoc.col);
+ errInfoData.relatedLocs.push_back(std::move(relLoc));
+ }
+ }
+
+ resultErrors.emplace_back(errInfoData);
+ }
+
+ return nonstd::make_unexpected(std::move(resultErrors));
+ }
+
+ Token MakeToken(Token::Type type, const CharRange& range, string_t value = string_t())
+ {
+ Token tok;
+ tok.type = type;
+ tok.range = range;
+ tok.value = TargetString(static_cast<string_t>(value));
+
+ return tok;
+ }
+
+ auto TokenToString(const Token& tok)
+ {
+ auto p = traits_t::s_tokens.find(tok.type);
+ if (p != traits_t::s_tokens.end())
+ return p->second.template GetValueStr<CharT>();
+
+ if (tok.range.size() != 0)
+ return string_t(m_template->substr(tok.range.startOffset, tok.range.size()));
+ else if (tok.type == Token::Identifier)
+ {
+ if (!tok.value.IsEmpty())
+ {
+ std::basic_string<CharT> tpl;
+ return GetAsSameString(tpl, tok.value).value_or(std::basic_string<CharT>());
+ }
+
+ return UNIVERSAL_STR("<<Identifier>>").template GetValueStr<CharT>();
+ }
+ else if (tok.type == Token::String)
+ return UNIVERSAL_STR("<<String>>").template GetValueStr<CharT>();
+
+ return string_t();
+ }
+
+ size_t FinishCurrentBlock(size_t position, TextBlockType nextBlockType, size_t matchStart = 0)
+ {
+ size_t newPos = position;
+
+ if (m_currentBlockInfo.type == TextBlockType::RawBlock || m_currentBlockInfo.type == TextBlockType::MetaBlock)
+ {
+ size_t currentPosition = matchStart ? matchStart : position;
+ auto origPos = position;
+ position = StripBlockLeft(m_currentBlockInfo, currentPosition + 2, currentPosition, m_settings.lstripBlocks);
+ newPos = StripBlockRight(m_currentBlockInfo, origPos, m_settings.trimBlocks);
+ }
+ else
+ {
+ if (m_currentBlockInfo.type == TextBlockType::RawText)
+ position =
+ StripBlockLeft(m_currentBlockInfo, position + 2, position, nextBlockType == TextBlockType::Expression ? false : m_settings.lstripBlocks);
+ else if (nextBlockType == TextBlockType::RawText)
+ newPos = StripBlockRight(m_currentBlockInfo, position, m_currentBlockInfo.type == TextBlockType::Expression ? false : m_settings.trimBlocks);
+
+ if ((m_currentBlockInfo.type != TextBlockType::RawText) && position != 0)
+ {
+ auto ctrlChar = (*m_template)[position - 1];
+ if (ctrlChar == '+' || ctrlChar == '-')
+ --position;
+ }
+ }
+
+ m_currentBlockInfo.range.endOffset = position;
+ m_textBlocks.push_back(m_currentBlockInfo);
+ m_currentBlockInfo.type = TextBlockType::RawText;
+ return newPos;
+ }
+
+ void FinishCurrentLine(int64_t position)
+ {
+ m_currentLineInfo.range.endOffset = static_cast<size_t>(position);
+ m_lines.push_back(m_currentLineInfo);
+ m_currentLineInfo.lineNumber++;
+ }
+
+ void OffsetToLinePos(size_t offset, unsigned& line, unsigned& col)
+ {
+ auto p = std::find_if(
+ m_lines.begin(), m_lines.end(), [offset](const LineInfo& info) { return offset >= info.range.startOffset && offset < info.range.endOffset; });
+
+ if (p == m_lines.end())
+ {
+ if (m_lines.empty() || offset != m_lines.back().range.endOffset)
+ {
+ line = 1;
+ col = 1;
+ return;
+ }
+ p = m_lines.end() - 1;
+ }
+
+ line = p->lineNumber + 1;
+ col = static_cast<unsigned>(offset - p->range.startOffset + 1);
+ }
+
+ string_t GetLocationDescr(unsigned line, unsigned col)
+ {
+ if (line == 0 && col == 0)
+ return string_t();
+
+ --line;
+ --col;
+
+ auto toCharT = [](char ch) { return static_cast<CharT>(ch); };
+
+ auto& lineInfo = m_lines[line];
+ std::basic_ostringstream<CharT> os;
+ auto origLine = m_template->substr(lineInfo.range.startOffset, lineInfo.range.size());
+ os << origLine << std::endl;
+
+ string_t spacePrefix;
+ auto locale = std::locale();
+ for (auto ch : origLine)
+ {
+ if (!std::isspace(ch, locale))
+ break;
+ spacePrefix.append(1, ch);
+ }
+
+ const int headLen = 3;
+ const int tailLen = 7;
+ auto spacePrefixLen = spacePrefix.size();
+
+ if (col < spacePrefixLen)
+ {
+ for (unsigned i = 0; i < col; ++i)
+ os << toCharT(' ');
+
+ os << toCharT('^');
+ for (int i = 0; i < tailLen; ++i)
+ os << toCharT('-');
+ return os.str();
+ }
+
+ os << spacePrefix;
+ int actualHeadLen = std::min(static_cast<int>(col - spacePrefixLen), headLen);
+
+ if (actualHeadLen == headLen)
+ {
+ for (std::size_t i = 0; i < col - actualHeadLen - spacePrefixLen; ++i)
+ os << toCharT(' ');
+ }
+ for (int i = 0; i < actualHeadLen; ++i)
+ os << toCharT('-');
+ os << toCharT('^');
+ for (int i = 0; i < tailLen; ++i)
+ os << toCharT('-');
+
+ return os.str();
+ }
+
+ // LexerHelper interface
+ std::string GetAsString(const CharRange& range) override { return traits_t::GetAsString(*m_template, range); }
+ InternalValue GetAsValue(const CharRange& range, Token::Type type) override
+ {
+ if (type == Token::String)
+ {
+ auto rawValue = CompileEscapes(m_template->substr(range.startOffset, range.size()));
+ return InternalValue(TargetString(std::move(rawValue)));
+ }
+ if (type == Token::IntegerNum || type == Token::FloatNum)
+ return traits_t::RangeToNum(*m_template, range, type);
+ return InternalValue();
+ }
+ Keyword GetKeyword(const CharRange& range) override
+ {
+ auto matchBegin = sregex_iterator(m_template->begin() + range.startOffset, m_template->begin() + range.endOffset, m_keywords);
+ auto matchEnd = sregex_iterator();
+
+ auto matches = std::distance(matchBegin, matchEnd);
+ // One line, no customization
+ if (matches == 0)
+ return Keyword::Unknown;
+
+ auto& match = *matchBegin;
+ for (size_t idx = 1; idx != match.size(); ++idx)
+ {
+ if (match.length(idx) != 0)
+ {
+ return traits_t::s_keywordsInfo[idx - 1].type;
+ }
+ }
+
+ return Keyword::Unknown;
+ }
+ char GetCharAt(size_t /*pos*/) override { return '\0'; }
+
+private:
+ const string_t* m_template;
+ std::string m_templateName;
+ const Settings& m_settings;
+ TemplateEnv* m_env = nullptr;
+ BasicRegex<CharT> m_roughTokenizer;
+ BasicRegex<CharT> m_keywords;
+ std::vector<LineInfo> m_lines;
+ std::vector<TextBlockInfo> m_textBlocks;
+ LineInfo m_currentLineInfo = {};
+ TextBlockInfo m_currentBlockInfo = {};
+ bool m_hasMetaBlock = false;
+ std::basic_string_view<CharT> m_metadata;
+ std::string m_metadataType;
+ SourceLocation m_metadataLocation;
+};
+
+template<typename T>
+KeywordsInfo ParserTraitsBase<T>::s_keywordsInfo[41] = {
+ { UNIVERSAL_STR("for"), Keyword::For },
+ { UNIVERSAL_STR("endfor"), Keyword::Endfor },
+ { UNIVERSAL_STR("in"), Keyword::In },
+ { UNIVERSAL_STR("if"), Keyword::If },
+ { UNIVERSAL_STR("else"), Keyword::Else },
+ { UNIVERSAL_STR("elif"), Keyword::ElIf },
+ { UNIVERSAL_STR("endif"), Keyword::EndIf },
+ { UNIVERSAL_STR("or"), Keyword::LogicalOr },
+ { UNIVERSAL_STR("and"), Keyword::LogicalAnd },
+ { UNIVERSAL_STR("not"), Keyword::LogicalNot },
+ { UNIVERSAL_STR("is"), Keyword::Is },
+ { UNIVERSAL_STR("block"), Keyword::Block },
+ { UNIVERSAL_STR("endblock"), Keyword::EndBlock },
+ { UNIVERSAL_STR("extends"), Keyword::Extends },
+ { UNIVERSAL_STR("macro"), Keyword::Macro },
+ { UNIVERSAL_STR("endmacro"), Keyword::EndMacro },
+ { UNIVERSAL_STR("call"), Keyword::Call },
+ { UNIVERSAL_STR("endcall"), Keyword::EndCall },
+ { UNIVERSAL_STR("filter"), Keyword::Filter },
+ { UNIVERSAL_STR("endfilter"), Keyword::EndFilter },
+ { UNIVERSAL_STR("set"), Keyword::Set },
+ { UNIVERSAL_STR("endset"), Keyword::EndSet },
+ { UNIVERSAL_STR("include"), Keyword::Include },
+ { UNIVERSAL_STR("import"), Keyword::Import },
+ { UNIVERSAL_STR("true"), Keyword::True },
+ { UNIVERSAL_STR("false"), Keyword::False },
+ { UNIVERSAL_STR("True"), Keyword::True },
+ { UNIVERSAL_STR("False"), Keyword::False },
+ { UNIVERSAL_STR("none"), Keyword::None },
+ { UNIVERSAL_STR("None"), Keyword::None },
+ { UNIVERSAL_STR("recursive"), Keyword::Recursive },
+ { UNIVERSAL_STR("scoped"), Keyword::Scoped },
+ { UNIVERSAL_STR("with"), Keyword::With },
+ { UNIVERSAL_STR("endwith"), Keyword::EndWith },
+ { UNIVERSAL_STR("without"), Keyword::Without },
+ { UNIVERSAL_STR("ignore"), Keyword::Ignore },
+ { UNIVERSAL_STR("missing"), Keyword::Missing },
+ { UNIVERSAL_STR("context"), Keyword::Context },
+ { UNIVERSAL_STR("from"), Keyword::From },
+ { UNIVERSAL_STR("as"), Keyword::As },
+ { UNIVERSAL_STR("do"), Keyword::Do },
+};
+
+template<typename T>
+std::unordered_map<int, MultiStringLiteral> ParserTraitsBase<T>::s_tokens = {
+ { Token::Unknown, UNIVERSAL_STR("<<Unknown>>") },
+ { Token::Lt, UNIVERSAL_STR("<") },
+ { Token::Gt, UNIVERSAL_STR(">") },
+ { Token::Plus, UNIVERSAL_STR("+") },
+ { Token::Minus, UNIVERSAL_STR("-") },
+ { Token::Percent, UNIVERSAL_STR("%") },
+ { Token::Mul, UNIVERSAL_STR("*") },
+ { Token::Div, UNIVERSAL_STR("/") },
+ { Token::LBracket, UNIVERSAL_STR("(") },
+ { Token::RBracket, UNIVERSAL_STR(")") },
+ { Token::LSqBracket, UNIVERSAL_STR("[") },
+ { Token::RSqBracket, UNIVERSAL_STR("]") },
+ { Token::LCrlBracket, UNIVERSAL_STR("{") },
+ { Token::RCrlBracket, UNIVERSAL_STR("}") },
+ { Token::Assign, UNIVERSAL_STR("=") },
+ { Token::Comma, UNIVERSAL_STR(",") },
+ { Token::Eof, UNIVERSAL_STR("<<End of block>>") },
+ { Token::Equal, UNIVERSAL_STR("==") },
+ { Token::NotEqual, UNIVERSAL_STR("!=") },
+ { Token::LessEqual, UNIVERSAL_STR("<=") },
+ { Token::GreaterEqual, UNIVERSAL_STR(">=") },
+ { Token::StarStar, UNIVERSAL_STR("**") },
+ { Token::DashDash, UNIVERSAL_STR("//") },
+ { Token::LogicalOr, UNIVERSAL_STR("or") },
+ { Token::LogicalAnd, UNIVERSAL_STR("and") },
+ { Token::LogicalNot, UNIVERSAL_STR("not") },
+ { Token::MulMul, UNIVERSAL_STR("**") },
+ { Token::DivDiv, UNIVERSAL_STR("//") },
+ { Token::True, UNIVERSAL_STR("true") },
+ { Token::False, UNIVERSAL_STR("false") },
+ { Token::None, UNIVERSAL_STR("none") },
+ { Token::In, UNIVERSAL_STR("in") },
+ { Token::Is, UNIVERSAL_STR("is") },
+ { Token::For, UNIVERSAL_STR("for") },
+ { Token::Endfor, UNIVERSAL_STR("endfor") },
+ { Token::If, UNIVERSAL_STR("if") },
+ { Token::Else, UNIVERSAL_STR("else") },
+ { Token::ElIf, UNIVERSAL_STR("elif") },
+ { Token::EndIf, UNIVERSAL_STR("endif") },
+ { Token::Block, UNIVERSAL_STR("block") },
+ { Token::EndBlock, UNIVERSAL_STR("endblock") },
+ { Token::Extends, UNIVERSAL_STR("extends") },
+ { Token::Macro, UNIVERSAL_STR("macro") },
+ { Token::EndMacro, UNIVERSAL_STR("endmacro") },
+ { Token::Call, UNIVERSAL_STR("call") },
+ { Token::EndCall, UNIVERSAL_STR("endcall") },
+ { Token::Filter, UNIVERSAL_STR("filter") },
+ { Token::EndFilter, UNIVERSAL_STR("endfilter") },
+ { Token::Set, UNIVERSAL_STR("set") },
+ { Token::EndSet, UNIVERSAL_STR("endset") },
+ { Token::Include, UNIVERSAL_STR("include") },
+ { Token::Import, UNIVERSAL_STR("import") },
+ { Token::Recursive, UNIVERSAL_STR("recursive") },
+ { Token::Scoped, UNIVERSAL_STR("scoped") },
+ { Token::With, UNIVERSAL_STR("with") },
+ { Token::EndWith, UNIVERSAL_STR("endwith") },
+ { Token::Without, UNIVERSAL_STR("without") },
+ { Token::Ignore, UNIVERSAL_STR("ignore") },
+ { Token::Missing, UNIVERSAL_STR("missing") },
+ { Token::Context, UNIVERSAL_STR("context") },
+ { Token::From, UNIVERSAL_STR("form") },
+ { Token::As, UNIVERSAL_STR("as") },
+ { Token::Do, UNIVERSAL_STR("do") },
+ { Token::RawBegin, UNIVERSAL_STR("{% raw %}") },
+ { Token::RawEnd, UNIVERSAL_STR("{% endraw %}") },
+ { Token::MetaBegin, UNIVERSAL_STR("{% meta %}") },
+ { Token::MetaEnd, UNIVERSAL_STR("{% endmeta %}") },
+ { Token::CommentBegin, UNIVERSAL_STR("{#") },
+ { Token::CommentEnd, UNIVERSAL_STR("#}") },
+ { Token::StmtBegin, UNIVERSAL_STR("{%") },
+ { Token::StmtEnd, UNIVERSAL_STR("%}") },
+ { Token::ExprBegin, UNIVERSAL_STR("{{") },
+ { Token::ExprEnd, UNIVERSAL_STR("}}") },
+};
+
+} // namespace jinja2
+
+#endif // JINJA2CPP_SRC_TEMPLATE_PARSER_H
diff --git a/contrib/libs/jinja2cpp/src/testers.cpp b/contrib/libs/jinja2cpp/src/testers.cpp
new file mode 100644
index 0000000000..ce9d255d82
--- /dev/null
+++ b/contrib/libs/jinja2cpp/src/testers.cpp
@@ -0,0 +1,379 @@
+#include "testers.h"
+#include "value_visitors.h"
+
+namespace jinja2
+{
+
+template<typename F>
+struct TesterFactory
+{
+ static TesterPtr Create(TesterParams params)
+ {
+ return std::make_shared<F>(std::move(params));
+ }
+
+ template<typename ... Args>
+ static IsExpression::TesterFactoryFn MakeCreator(Args&& ... args)
+ {
+ return [args...](TesterParams params) {return std::make_shared<F>(std::move(params), args...);};
+ }
+};
+
+std::unordered_map<std::string, IsExpression::TesterFactoryFn> s_testers = {
+ {"defined", TesterFactory<testers::ValueTester>::MakeCreator(testers::ValueTester::IsDefinedMode)},
+ {"startsWith", &TesterFactory<testers::StartsWith>::Create},
+ {"eq", TesterFactory<testers::Comparator>::MakeCreator(BinaryExpression::LogicalEq)},
+ {"==", TesterFactory<testers::Comparator>::MakeCreator(BinaryExpression::LogicalEq)},
+ {"equalto", TesterFactory<testers::Comparator>::MakeCreator(BinaryExpression::LogicalEq)},
+ {"even", TesterFactory<testers::ValueTester>::MakeCreator(testers::ValueTester::IsEvenMode)},
+ {"ge", TesterFactory<testers::Comparator>::MakeCreator(BinaryExpression::LogicalGe)},
+ {">=", TesterFactory<testers::Comparator>::MakeCreator(BinaryExpression::LogicalGe)},
+ {"gt", TesterFactory<testers::Comparator>::MakeCreator(BinaryExpression::LogicalGt)},
+ {">", TesterFactory<testers::Comparator>::MakeCreator(BinaryExpression::LogicalGt)},
+ {"greaterthan", TesterFactory<testers::Comparator>::MakeCreator(BinaryExpression::LogicalGt)},
+ {"in", TesterFactory<testers::ValueTester>::MakeCreator(testers::ValueTester::IsInMode)},
+ {"iterable", TesterFactory<testers::ValueTester>::MakeCreator(testers::ValueTester::IsIterableMode)},
+ {"le", TesterFactory<testers::Comparator>::MakeCreator(BinaryExpression::LogicalLe)},
+ {"<=", TesterFactory<testers::Comparator>::MakeCreator(BinaryExpression::LogicalLe)},
+ {"lower", TesterFactory<testers::ValueTester>::MakeCreator(testers::ValueTester::IsLowerMode)},
+ {"lt", TesterFactory<testers::Comparator>::MakeCreator(BinaryExpression::LogicalLt)},
+ {"<", TesterFactory<testers::Comparator>::MakeCreator(BinaryExpression::LogicalLt)},
+ {"lessthan", TesterFactory<testers::Comparator>::MakeCreator(BinaryExpression::LogicalLt)},
+ {"mapping", TesterFactory<testers::ValueTester>::MakeCreator(testers::ValueTester::IsMappingMode)},
+ {"ne", TesterFactory<testers::Comparator>::MakeCreator(BinaryExpression::LogicalNe)},
+ {"!=", TesterFactory<testers::Comparator>::MakeCreator(BinaryExpression::LogicalNe)},
+ {"number", TesterFactory<testers::ValueTester>::MakeCreator(testers::ValueTester::IsNumberMode)},
+ {"odd", TesterFactory<testers::ValueTester>::MakeCreator(testers::ValueTester::IsOddMode)},
+ {"sequence", TesterFactory<testers::ValueTester>::MakeCreator(testers::ValueTester::IsSequenceMode)},
+ {"string", TesterFactory<testers::ValueTester>::MakeCreator(testers::ValueTester::IsStringMode)},
+ {"undefined", TesterFactory<testers::ValueTester>::MakeCreator(testers::ValueTester::IsUndefinedMode)},
+ {"upper", TesterFactory<testers::ValueTester>::MakeCreator(testers::ValueTester::IsUpperMode)},
+};
+
+TesterPtr CreateTester(std::string testerName, CallParamsInfo params)
+{
+ auto p = s_testers.find(testerName);
+ if (p == s_testers.end())
+ return std::make_shared<testers::UserDefinedTester>(std::move(testerName), std::move(params));
+
+ return p->second(std::move(params));
+}
+
+namespace testers
+{
+
+Comparator::Comparator(TesterParams params, BinaryExpression::Operation op)
+ : m_op(op)
+{
+ ParseParams({{"b", true}}, params);
+}
+
+bool Comparator::Test(const InternalValue& baseVal, RenderContext& context)
+{
+ auto b = GetArgumentValue("b", context);
+
+ auto cmpRes = Apply2<visitors::BinaryMathOperation>(baseVal, b, m_op);
+ return ConvertToBool(cmpRes);
+}
+
+#if 0
+bool Defined::Test(const InternalValue& baseVal, RenderContext& /*context*/)
+{
+ return boost::get<EmptyValue>(&baseVal) == nullptr;
+}
+#endif
+
+StartsWith::StartsWith(TesterParams params)
+{
+ bool parsed = true;
+ auto args = helpers::ParseCallParamsInfo({ { "str", true } }, params, parsed);
+ m_stringEval = args["str"];
+}
+
+bool StartsWith::Test(const InternalValue& baseVal, RenderContext& context)
+{
+ InternalValue val = m_stringEval->Evaluate(context);
+ std::string baseStr = AsString(baseVal);
+ std::string str = AsString(val);
+ return baseStr.find(str) == 0;
+}
+
+ValueTester::ValueTester(TesterParams params, ValueTester::Mode mode)
+ : m_mode(mode)
+{
+ switch (m_mode)
+ {
+ case IsDefinedMode:
+ break;
+ case IsEvenMode:
+ break;
+ case IsInMode:
+ ParseParams({{"seq", true}}, params);
+ break;
+ case IsIterableMode:
+ break;
+ case IsLowerMode:
+ break;
+ case IsMappingMode:
+ break;
+ case IsNumberMode:
+ break;
+ case IsOddMode:
+ break;
+ case IsSequenceMode:
+ break;
+ case IsStringMode:
+ break;
+ case IsUndefinedMode:
+ break;
+ case IsUpperMode:
+ break;
+
+ }
+}
+
+enum class ValueKind
+{
+ Empty,
+ Boolean,
+ String,
+ Integer,
+ Double,
+ List,
+ Map,
+ KVPair,
+ Callable,
+ Renderer
+};
+
+struct ValueKindGetter : visitors::BaseVisitor<ValueKind>
+{
+ using visitors::BaseVisitor<ValueKind>::operator ();
+
+ ValueKind operator()(const EmptyValue&) const
+ {
+ return ValueKind::Empty;
+ }
+ ValueKind operator()(bool) const
+ {
+ return ValueKind::Boolean;
+ }
+ template<typename CharT>
+ ValueKind operator()(const std::basic_string<CharT>&) const
+ {
+ return ValueKind::String;
+ }
+ template<typename CharT>
+ ValueKind operator()(const std::basic_string_view<CharT>&) const
+ {
+ return ValueKind::String;
+ }
+ ValueKind operator()(int64_t) const
+ {
+ return ValueKind::Integer;
+ }
+ ValueKind operator()(double) const
+ {
+ return ValueKind::Double;
+ }
+ ValueKind operator()(const ListAdapter&) const
+ {
+ return ValueKind::List;
+ }
+ ValueKind operator()(const MapAdapter&) const
+ {
+ return ValueKind::Map;
+ }
+ ValueKind operator()(const KeyValuePair&) const
+ {
+ return ValueKind::KVPair;
+ }
+ ValueKind operator()(const Callable&) const
+ {
+ return ValueKind::Callable;
+ }
+ ValueKind operator()(IRendererBase*) const
+ {
+ return ValueKind::Renderer;
+ }
+};
+
+bool ValueTester::Test(const InternalValue& baseVal, RenderContext& context)
+{
+ bool result = false;
+ auto valKind = Apply<ValueKindGetter>(baseVal);
+ enum
+ {
+ EvenTest,
+ OddTest
+ };
+
+ int testMode = EvenTest;
+ auto evenOddTest = [&testMode, valKind](const InternalValue& val) -> bool
+ {
+ bool result = false;
+ if (valKind == ValueKind::Integer)
+ {
+ auto intVal = ConvertToInt(val);
+ result = (intVal & 1) == (testMode == EvenTest ? 0 : 1);
+ }
+ else if (valKind == ValueKind::Double)
+ {
+ auto dblVal = ConvertToDouble(val);
+ int64_t intVal = static_cast<int64_t>(dblVal);
+ if (dblVal == intVal)
+ result = (intVal & 1) == (testMode == EvenTest ? 0 : 1);
+ }
+ return result;
+ };
+
+ switch (m_mode)
+ {
+ case IsIterableMode:
+ result = valKind == ValueKind::List || valKind == ValueKind::Map;
+ break;
+ case IsMappingMode:
+ result = valKind == ValueKind::KVPair || valKind == ValueKind::Map;
+ break;
+ case IsNumberMode:
+ result = valKind == ValueKind::Integer || valKind == ValueKind::Double;
+ break;
+ case IsSequenceMode:
+ result = valKind == ValueKind::List;
+ break;
+ case IsStringMode:
+ result = valKind == ValueKind::String;
+ break;
+ case IsDefinedMode:
+ result = valKind != ValueKind::Empty;
+ break;
+ case IsUndefinedMode:
+ result = valKind == ValueKind::Empty;
+ break;
+ case IsInMode:
+ {
+ bool isConverted = false;
+ auto seq = GetArgumentValue("seq", context);
+ auto seqKind = Apply<ValueKindGetter>(seq);
+ if (seqKind == ValueKind::List) {
+ ListAdapter values = ConvertToList(seq, InternalValue(), isConverted);
+
+ if (!isConverted)
+ return false;
+
+ auto equalComparator = [&baseVal](auto& val) {
+ InternalValue cmpRes;
+ cmpRes = Apply2<visitors::BinaryMathOperation>(val, baseVal, BinaryExpression::LogicalEq);
+ return ConvertToBool(cmpRes);
+ };
+
+ auto p = std::find_if(values.begin(), values.end(), equalComparator);
+ result = p != values.end();
+ } else if (seqKind == ValueKind::String) {
+ result = ApplyStringConverter(baseVal, [&](const auto& srcStr) {
+ std::decay_t<decltype(srcStr)> emptyStrView;
+ using CharT = typename decltype(emptyStrView)::value_type;
+ std::basic_string<CharT> emptyStr;
+
+ auto substring = sv_to_string(srcStr);
+ auto seq = GetAsSameString(srcStr, this->GetArgumentValue("seq", context)).value_or(emptyStr);
+
+ return seq.find(substring) != std::string::npos;
+ });
+ }
+ break;
+ }
+ case IsEvenMode:
+ {
+ testMode = EvenTest;
+ result = evenOddTest(baseVal);
+ break;
+ }
+ case IsOddMode:
+ {
+ testMode = OddTest;
+ result = evenOddTest(baseVal);
+ break;
+ }
+ case IsLowerMode:
+ if (valKind != ValueKind::String)
+ {
+ result = false;
+ }
+ else
+ {
+ result = ApplyStringConverter(baseVal, [](const auto& str) {
+ bool result = true;
+ for (auto& ch : str)
+ {
+ if (std::isalpha(ch, std::locale()) && std::isupper(ch, std::locale()))
+ {
+ result = false;
+ break;
+ }
+ }
+ return result;
+ });
+ }
+ break;
+ case IsUpperMode:
+ if (valKind != ValueKind::String)
+ {
+ result = false;
+ }
+ else
+ {
+ result = ApplyStringConverter(baseVal, [](const auto& str) {
+ bool result = true;
+ for (auto& ch : str)
+ {
+ if (std::isalpha(ch, std::locale()) && std::islower(ch, std::locale()))
+ {
+ result = false;
+ break;
+ }
+ }
+ return result;
+ });
+ }
+ break;
+
+ }
+ return result;
+}
+
+UserDefinedTester::UserDefinedTester(std::string testerName, TesterParams params)
+ : m_testerName(std::move(testerName))
+{
+ ParseParams({{"*args"}, {"**kwargs"}}, params);
+ m_callParams.kwParams = m_args.extraKwArgs;
+ m_callParams.posParams = m_args.extraPosArgs;
+}
+
+bool UserDefinedTester::Test(const InternalValue& baseVal, RenderContext& context)
+{
+ bool testerFound = false;
+ auto testerValPtr = context.FindValue(m_testerName, testerFound);
+ if (!testerFound)
+ return false;
+
+ const Callable* callable = GetIf<Callable>(&testerValPtr->second);
+ if (callable == nullptr || callable->GetKind() != Callable::UserCallable)
+ return false;
+
+ CallParams tmpCallParams = helpers::EvaluateCallParams(m_callParams, context);
+ CallParams callParams;
+ callParams.kwParams = std::move(tmpCallParams.kwParams);
+ callParams.posParams.reserve(tmpCallParams.posParams.size() + 1);
+ callParams.posParams.push_back(baseVal);
+ for (auto& p : tmpCallParams.posParams)
+ callParams.posParams.push_back(std::move(p));
+
+ InternalValue result;
+ if (callable->GetType() != Callable::Type::Expression)
+ return false;
+
+ return ConvertToBool(callable->GetExpressionCallable()(callParams, context));
+}
+} // namespace testers
+} // namespace jinja2
diff --git a/contrib/libs/jinja2cpp/src/testers.h b/contrib/libs/jinja2cpp/src/testers.h
new file mode 100644
index 0000000000..9d29e6cbc1
--- /dev/null
+++ b/contrib/libs/jinja2cpp/src/testers.h
@@ -0,0 +1,116 @@
+#ifndef JINJA2CPP_SRC_TESTERS_H
+#define JINJA2CPP_SRC_TESTERS_H
+
+#include "expression_evaluator.h"
+#include "function_base.h"
+#include "jinja2cpp/value.h"
+#include "render_context.h"
+
+#include <memory>
+#include <functional>
+
+namespace jinja2
+{
+using TesterPtr = std::shared_ptr<IsExpression::ITester>;
+using TesterParams = CallParamsInfo;
+
+extern TesterPtr CreateTester(std::string testerName, CallParamsInfo params);
+
+namespace testers
+{
+
+class TesterBase : public FunctionBase, public IsExpression::ITester
+{
+};
+
+class Comparator : public TesterBase
+{
+public:
+ 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<const Comparator*>(&other);
+ if (!val)
+ return false;
+ return m_op == val->m_op;
+ }
+private:
+ BinaryExpression::Operation m_op;
+};
+
+class StartsWith : public IsExpression::ITester
+{
+public:
+ StartsWith(TesterParams);
+
+ bool Test(const InternalValue& baseVal, RenderContext& context) override;
+
+ bool IsEqual(const IComparable& other) const override
+ {
+ auto* val = dynamic_cast<const StartsWith*>(&other);
+ if (!val)
+ return false;
+ return m_stringEval == val->m_stringEval;
+ }
+private:
+ ExpressionEvaluatorPtr<> m_stringEval;
+};
+
+class ValueTester : public TesterBase
+{
+public:
+ enum Mode
+ {
+ IsDefinedMode,
+ IsEvenMode,
+ IsInMode,
+ IsIterableMode,
+ IsLowerMode,
+ IsMappingMode,
+ IsNumberMode,
+ IsOddMode,
+ IsSequenceMode,
+ IsStringMode,
+ IsUndefinedMode,
+ IsUpperMode
+ };
+
+ ValueTester(TesterParams params, Mode mode);
+
+ bool Test(const InternalValue& baseVal, RenderContext& context) override;
+
+ bool IsEqual(const IComparable& other) const override
+ {
+ auto* val = dynamic_cast<const ValueTester*>(&other);
+ if (!val)
+ return false;
+ return m_mode == val->m_mode;
+ }
+private:
+ Mode m_mode;
+};
+
+class UserDefinedTester : public TesterBase
+{
+public:
+ UserDefinedTester(std::string filterName, TesterParams params);
+
+ bool Test(const InternalValue& baseVal, RenderContext& context) override;
+
+ bool IsEqual(const IComparable& other) const override
+ {
+ auto* val = dynamic_cast<const UserDefinedTester*>(&other);
+ if (!val)
+ return false;
+ return m_testerName == val->m_testerName && m_callParams == val->m_callParams;
+ }
+private:
+ std::string m_testerName;
+ TesterParams m_callParams;
+};
+} // namespace testers
+} // namespace jinja2
+
+#endif // JINJA2CPP_SRC_TESTERS_H
diff --git a/contrib/libs/jinja2cpp/src/value.cpp b/contrib/libs/jinja2cpp/src/value.cpp
new file mode 100644
index 0000000000..3139b29ac2
--- /dev/null
+++ b/contrib/libs/jinja2cpp/src/value.cpp
@@ -0,0 +1,143 @@
+#if 0
+#include "jinja2cpp/value.h"
+#include <sstream>
+
+namespace jinja2
+{
+template<typename T>
+std::string toString(T val)
+{
+ std::ostringstream os;
+ os << val;
+ return os.str();
+}
+
+namespace
+{
+struct ValueRenderer : boost::static_visitor<std::string>
+{
+ std::string operator() (bool val) const
+ {
+ return val ? "True" : "False";
+ }
+ std::string operator() (const EmptyValue&) const
+ {
+ return std::string();
+ }
+ std::string operator() (const std::wstring&) const
+ {
+ return std::string();
+ }
+
+ std::string operator() (const ValuesList& vals) const
+ {
+ std::string result = "{";
+ bool isFirst = true;
+ for (auto& val : vals)
+ {
+ if (isFirst)
+ isFirst = false;
+ else
+ result += ", ";
+
+ result += boost::apply_visitor(ValueRenderer(), val.data());
+ }
+ result += "}";
+ return result;
+ }
+
+ std::string operator() (const ValuesMap& vals) const
+ {
+ std::string result = "{";
+ bool isFirst = true;
+ for (auto& val : vals)
+ {
+ if (isFirst)
+ isFirst = false;
+ else
+ result += ", ";
+
+ result += "{\"" + val.first + "\",";
+ result += boost::apply_visitor(ValueRenderer(), val.second.data());
+ result += "}";
+ }
+ result += "}";
+ return result;
+ }
+
+ std::string operator() (const GenericMap& /*val*/) const
+ {
+ return "";
+ }
+
+ std::string operator() (const GenericList& /*val*/) const
+ {
+ return "";
+ }
+
+ template<typename T>
+ std::string operator() (const T& val) const
+ {
+ return toString(val);
+ }
+};
+
+struct SubscriptionVisitor : public boost::static_visitor<InternalValue>
+{
+ InternalValue operator() (const ValuesMap& values, const std::string& field) const
+ {
+ auto p = values.find(field);
+ if (p == values.end())
+ return InternalValue();
+
+ return p->second;
+ }
+
+ InternalValue operator() (const GenericMap& values, const std::string& field) const
+ {
+ if (!values.HasValue(field))
+ return InternalValue();
+
+ return values.GetValueByName(field);
+ }
+
+ InternalValue operator() (const GenericMap& values, const int64_t index) const
+ {
+ if (index < 0 || static_cast<size_t>(index) >= values.GetSize())
+ return InternalValue();
+
+ return values.GetValueByIndex(index);
+ }
+
+ InternalValue operator() (const ValuesList& values, int64_t index) const
+ {
+ if (index < 0 || static_cast<size_t>(index) >= values.size())
+ return InternalValue();
+
+ return values[static_cast<size_t>(index)];
+ }
+
+ InternalValue operator() (const GenericList& values, const int64_t index) const
+ {
+ if (index < 0 || static_cast<size_t>(index) >= values.GetSize())
+ return InternalValue();
+
+ return values.GetValueByIndex(index);
+ }
+
+ template<typename T, typename U>
+ InternalValue operator() (T&& /*first*/, U&& /*second*/) const
+ {
+ return InternalValue();
+ }
+};
+
+} //
+
+InternalValue InternalValue::subscript(const InternalValue& index) const
+{
+ return boost::apply_visitor(SubscriptionVisitor(), m_data, index.m_data);
+}
+
+} // jinja2
+#endif
diff --git a/contrib/libs/jinja2cpp/src/value_helpers.h b/contrib/libs/jinja2cpp/src/value_helpers.h
new file mode 100644
index 0000000000..d5ac829fc4
--- /dev/null
+++ b/contrib/libs/jinja2cpp/src/value_helpers.h
@@ -0,0 +1,167 @@
+#ifndef JINJA2CPP_SRC_VALUE_HELPERS_H
+#define JINJA2CPP_SRC_VALUE_HELPERS_H
+
+#include <jinja2cpp/value.h>
+
+#include <boost/iterator/iterator_facade.hpp>
+
+namespace jinja2
+{
+#if 0
+class GenericListIterator
+ : public boost::iterator_facade<
+ GenericListIterator,
+ const InternalValue,
+ boost::random_access_traversal_tag>
+{
+public:
+ GenericListIterator()
+ : m_current(0)
+ , m_list(nullptr)
+ {}
+
+ explicit GenericListIterator(GenericList& list)
+ : m_current(0)
+ , m_list(&list)
+ {}
+
+private:
+ friend class boost::iterator_core_access;
+
+ void increment()
+ {
+ ++ m_current;
+ m_valueIdx = m_current;
+ m_currentVal = m_current == m_list->GetSize() ? InternalValue() : m_list->GetValueByIndex(static_cast<int64_t>(m_current));
+ }
+
+ int distance_to(const GenericListIterator& other) const
+ {
+ if (m_list == nullptr)
+ return other.m_list == nullptr ? 0 : -other.distance_to(*this);
+
+ if (other.m_list == nullptr)
+ return m_list->GetSize() - m_current;
+
+ return other.m_current - m_current;
+ }
+
+ void advance(int distance)
+ {
+ m_current += distance;
+ if (distance != 0)
+ {
+ m_valueIdx = m_current;
+ m_currentVal = m_current == m_list->GetSize() ? InternalValue() : m_list->GetValueByIndex(static_cast<int64_t>(m_current));
+
+ }
+ }
+
+ bool equal(const GenericListIterator& other) const
+ {
+ if (m_list == nullptr)
+ return other.m_list == nullptr ? true : other.equal(*this);
+
+ if (other.m_list == nullptr)
+ return m_current == m_list->GetSize();
+
+ return this->m_list == other.m_list && this->m_current == other.m_current;
+ }
+
+ const InternalValue& dereference() const
+ {
+ if (m_current != m_valueIdx)
+ m_currentVal = m_current == m_list->GetSize() ? InternalValue() : m_list->GetValueByIndex(static_cast<int64_t>(m_current));
+ return m_currentVal;
+ }
+
+ int64_t m_current = 0;
+ mutable int64_t m_valueIdx = -1;
+ mutable InternalValue m_currentVal;
+ GenericList* m_list;
+};
+
+class ConstGenericListIterator
+ : public boost::iterator_facade<
+ GenericListIterator,
+ const InternalValue,
+ boost::random_access_traversal_tag>
+{
+public:
+ ConstGenericListIterator()
+ : m_current(0)
+ , m_list(nullptr)
+ {}
+
+ explicit ConstGenericListIterator(const GenericList& list)
+ : m_current(0)
+ , m_list(&list)
+ {}
+
+private:
+ friend class boost::iterator_core_access;
+
+ void increment()
+ {
+ ++ m_current;
+ }
+
+ int distance_to(const ConstGenericListIterator& other) const
+ {
+ if (m_list == nullptr)
+ return other.m_list == nullptr ? 0 : -other.distance_to(*this);
+
+ if (other.m_list == nullptr)
+ return m_list->GetSize() - m_current;
+
+ return other.m_current - m_current;
+ }
+
+ void advance(int distance)
+ {
+ m_current += distance;
+ }
+
+ bool equal(const ConstGenericListIterator& other) const
+ {
+ if (m_list == nullptr)
+ return other.m_list == nullptr ? true : other.equal(*this);
+
+ if (other.m_list == nullptr)
+ return m_current == m_list->GetSize();
+
+ return this->m_list == other.m_list && this->m_current == other.m_current;
+ }
+
+ const InternalValue& dereference() const
+ {
+ return m_list->GetValueByIndex(static_cast<int64_t>(m_current));
+ }
+
+ size_t m_current;
+ const GenericList* m_list;
+};
+
+inline auto begin(GenericList& list)
+{
+ return GenericListIterator(list);
+}
+
+inline auto end(GenericList& list)
+{
+ return GenericListIterator();
+}
+
+inline auto begin(const GenericList& list)
+{
+ return ConstGenericListIterator(list);
+}
+
+inline auto end(const GenericList& list)
+{
+ return ConstGenericListIterator();
+}
+#endif
+} // namespace jinja2
+
+#endif // JINJA2CPP_SRC_VALUE_HELPERS_H
diff --git a/contrib/libs/jinja2cpp/src/value_visitors.h b/contrib/libs/jinja2cpp/src/value_visitors.h
new file mode 100644
index 0000000000..8814f645cf
--- /dev/null
+++ b/contrib/libs/jinja2cpp/src/value_visitors.h
@@ -0,0 +1,1151 @@
+#ifndef JINJA2CPP_SRC_VALUE_VISITORS_H
+#define JINJA2CPP_SRC_VALUE_VISITORS_H
+
+#include "expression_evaluator.h"
+#include "helpers.h"
+#include "jinja2cpp/value.h"
+
+#include <boost/algorithm/string/predicate.hpp>
+#include <boost/optional.hpp>
+#include <fmt/format.h>
+#include <fmt/xchar.h>
+
+#include <iostream>
+#include <cmath>
+#include <limits>
+#include <utility>
+#include <typeinfo>
+
+namespace jinja2
+{
+
+namespace detail
+{
+
+template<typename V>
+struct RecursiveUnwrapper
+{
+ V* m_visitor{};
+
+ RecursiveUnwrapper(V* v)
+ : m_visitor(v)
+ {}
+
+
+ template<typename T>
+ static const auto& UnwrapRecursive(const T& arg)
+ {
+ return arg; // std::forward<T>(arg);
+ }
+
+ template<typename T>
+ static auto& UnwrapRecursive(const RecursiveWrapper<T>& arg)
+ {
+ return arg.GetValue();
+ }
+
+// template<typename T>
+// static auto& UnwrapRecursive(RecursiveWrapper<T>& arg)
+// {
+// return arg.GetValue();
+// }
+
+ template<typename ... Args>
+ auto operator()(const Args& ... args) const
+ {
+ assert(m_visitor != nullptr);
+ return (*m_visitor)(UnwrapRecursive(args)...);
+ }
+};
+
+template<typename Fn>
+auto ApplyUnwrapped(const InternalValueData& val, Fn&& fn)
+{
+ auto valueRef = GetIf<ValueRef>(&val);
+ auto targetString = GetIf<TargetString>(&val);
+ auto targetSV = GetIf<TargetStringView>(&val);
+ // auto internalValueRef = GetIf<InternalValueRef>(&val);
+
+ if (valueRef != nullptr)
+ return fn(valueRef->get().data());
+ else if (targetString != nullptr)
+ return fn(*targetString);
+ else if (targetSV != nullptr)
+ return fn(*targetSV);
+// else if (internalValueRef != nullptr)
+// return fn(internalValueRef->get());
+
+ return fn(val);
+}
+} // namespace detail
+
+template<typename V, typename ... Args>
+auto Apply(const InternalValue& val, Args&& ... args)
+{
+ return detail::ApplyUnwrapped(val.GetData(), [&args...](auto& val) {
+ auto v = V(args...);
+ return std::visit(detail::RecursiveUnwrapper<V>(&v), val);
+ });
+}
+
+template<typename V, typename ... Args>
+auto Apply2(const InternalValue& val1, const InternalValue& val2, Args&& ... args)
+{
+ return detail::ApplyUnwrapped(val1.GetData(), [&val2, &args...](auto& uwVal1) {
+ return detail::ApplyUnwrapped(val2.GetData(), [&uwVal1, &args...](auto& uwVal2) {
+ auto v = V(args...);
+ return std::visit(detail::RecursiveUnwrapper<V>(&v), uwVal1, uwVal2);
+ });
+ });
+}
+
+bool ConvertToBool(const InternalValue& val);
+
+namespace visitors
+{
+template<typename R = InternalValue>
+struct BaseVisitor
+{
+ R operator() (const GenericMap&) const
+ {
+ assert(false);
+ return R();
+ }
+
+ R operator() (const GenericList&) const
+ {
+ assert(false);
+ return R();
+ }
+
+ R operator() (const ValueRef&) const
+ {
+ assert(false);
+ return R();
+ }
+
+ R operator() (const TargetString&) const
+ {
+ assert(false);
+ return R();
+ }
+
+ template<typename T>
+ R operator() (T&&) const
+ {
+ return R();
+ }
+
+ template<typename T, typename U>
+ R operator() (T&&, U&&) const
+ {
+ return R();
+ }
+};
+
+
+template<typename CharT>
+struct ValueRendererBase
+{
+ ValueRendererBase(std::basic_string<CharT>& os)
+ : m_os(&os)
+ {
+ }
+
+ template<typename T>
+ void operator()(const T& val) const;
+ void operator()(double val) const;
+ void operator()(const std::basic_string_view<CharT>& val) const
+ {
+ m_os->append(val.begin(), val.end());
+ }
+ void operator()(const std::basic_string<CharT>& val) const
+ {
+ m_os->append(val.begin(), val.end());
+ }
+
+ void operator()(const EmptyValue&) const {}
+ void operator()(const ValuesList&) const {}
+ void operator()(const ValuesMap&) const {}
+ void operator()(const GenericMap&) const {}
+ void operator()(const GenericList&) const {}
+ void operator()(const MapAdapter&) const {}
+ void operator()(const ListAdapter&) const {}
+ void operator()(const ValueRef&) const {}
+ void operator()(const TargetString&) const {}
+ void operator()(const TargetStringView&) const {}
+ void operator()(const KeyValuePair&) const {}
+ void operator()(const Callable&) const {}
+ void operator()(const UserCallable&) const {}
+ void operator()(const std::shared_ptr<IRendererBase>) const {}
+ template<typename T>
+ void operator()(const boost::recursive_wrapper<T>&) const {}
+ template<typename T>
+ void operator()(const RecWrapper<T>&) const {}
+
+ auto GetOs() const { return std::back_inserter(*m_os); }
+
+ std::basic_string<CharT>* m_os;
+};
+
+template<>
+template<typename T>
+void ValueRendererBase<char>::operator()(const T& val) const
+{
+ fmt::format_to(GetOs(), "{}", val);
+}
+
+template<>
+template<typename T>
+void ValueRendererBase<wchar_t>::operator()(const T& val) const
+{
+ fmt::format_to(GetOs(), L"{}", val);
+}
+
+template<>
+inline void ValueRendererBase<char>::operator()(double val) const
+{
+ fmt::format_to(GetOs(), "{:.8g}", val);
+}
+
+template<>
+inline void ValueRendererBase<wchar_t>::operator()(double val) const
+{
+ fmt::format_to(GetOs(), L"{:.8g}", val);
+}
+
+struct InputValueConvertor
+{
+ using result_t = boost::optional<InternalValue>;
+
+ InputValueConvertor(bool byValue, bool allowStringRef)
+ : m_byValue(byValue)
+ , m_allowStringRef(allowStringRef)
+ {
+ }
+
+ template<typename ChT>
+ result_t operator()(const std::basic_string<ChT>& val) const
+ {
+ if (m_allowStringRef)
+ return result_t(TargetStringView(std::basic_string_view<ChT>(val)));
+
+ return result_t(TargetString(val));
+ }
+
+ template<typename ChT>
+ result_t operator()(std::basic_string<ChT>& val) const
+ {
+ return result_t(TargetString(std::move(val)));
+ }
+
+ result_t operator()(const ValuesList& vals) const
+ {
+ if (m_byValue)
+ {
+ ValuesList newVals(vals);
+ return result_t(InternalValue(ListAdapter::CreateAdapter(std::move(newVals))));
+ }
+
+ return result_t(InternalValue(ListAdapter::CreateAdapter(vals)));
+ }
+
+ result_t operator() (ValuesList& vals) const
+ {
+ return result_t(InternalValue(ListAdapter::CreateAdapter(std::move(vals))));
+ }
+
+ result_t operator() (const GenericList& vals) const
+ {
+ if (m_byValue)
+ {
+ GenericList newVals(vals);
+ return result_t(InternalValue(ListAdapter::CreateAdapter(std::move(newVals))));
+ }
+
+ return result_t(InternalValue(ListAdapter::CreateAdapter(vals)));
+ }
+
+ result_t operator() (GenericList& vals) const
+ {
+ return result_t(InternalValue(ListAdapter::CreateAdapter(std::move(vals))));
+ }
+
+ result_t operator()(const ValuesMap& vals) const
+ {
+ if (m_byValue)
+ {
+ ValuesMap newVals(vals);
+ return result_t(CreateMapAdapter(std::move(newVals)));
+ }
+
+ return result_t(CreateMapAdapter(vals));
+ }
+
+ result_t operator()(ValuesMap& vals) const { return result_t(CreateMapAdapter(std::move(vals))); }
+
+ result_t operator()(const GenericMap& vals) const
+ {
+ if (m_byValue)
+ {
+ GenericMap newVals(vals);
+ return result_t(CreateMapAdapter(std::move(newVals)));
+ }
+
+ return result_t(CreateMapAdapter(vals));
+ }
+
+ result_t operator()(GenericMap& vals) const { return result_t(CreateMapAdapter(std::move(vals))); }
+
+ result_t operator()(const UserCallable& val) const { return ConvertUserCallable(val); }
+
+ result_t operator()(UserCallable& val) const { return ConvertUserCallable(std::move(val)); }
+
+ template<typename T>
+ result_t operator()(const RecWrapper<T>& val) const
+ {
+ return this->operator()(const_cast<const T&>(*val));
+ }
+
+ template<typename T>
+ result_t operator()(RecWrapper<T>& val) const
+ {
+ return this->operator()(*val);
+ }
+
+ template<typename T>
+ result_t operator()(const T& val) const
+ {
+ return result_t(InternalValue(val));
+ }
+
+ static result_t ConvertUserCallable(const UserCallable& val);
+
+ bool m_byValue{};
+ bool m_allowStringRef{};
+};
+
+template<typename CharT>
+struct ValueRenderer;
+
+template<>
+struct ValueRenderer<char> : ValueRendererBase<char>
+{
+ ValueRenderer(std::string& os)
+ : ValueRendererBase<char>::ValueRendererBase<char>(os)
+ {
+ }
+
+ using ValueRendererBase<char>::operator ();
+ void operator()(const std::wstring& str) const
+ {
+ (*m_os) += ConvertString<std::string>(str);
+ }
+ void operator()(const std::wstring_view& str) const
+ {
+ (*m_os) += ConvertString<std::string>(str);
+ }
+ void operator() (bool val) const
+ {
+ m_os->append(val ? "true" : "false");
+ }
+};
+
+template<>
+struct ValueRenderer<wchar_t> : ValueRendererBase<wchar_t>
+{
+ ValueRenderer(std::wstring& os)
+ : ValueRendererBase<wchar_t>::ValueRendererBase<wchar_t>(os)
+ {
+ }
+
+ using ValueRendererBase<wchar_t>::operator ();
+ void operator()(const std::string& str) const
+ {
+ (*m_os) += ConvertString<std::wstring>(str);
+ }
+ void operator()(const std::string_view& str) const
+ {
+ (*m_os) += ConvertString<std::wstring>(str);
+ }
+ void operator() (bool val) const
+ {
+ // fmt::format_to(GetOs(), L"{}", (const wchar_t*)(val ? "true" : "false"));
+ m_os->append(val ? L"true" : L"false");
+ }
+};
+
+struct UnaryOperation : BaseVisitor<InternalValue>
+{
+ using BaseVisitor::operator ();
+
+ UnaryOperation(UnaryExpression::Operation oper)
+ : m_oper(oper)
+ {
+ }
+
+ InternalValue operator() (int64_t val) const
+ {
+ InternalValue result;
+ switch (m_oper)
+ {
+ case jinja2::UnaryExpression::LogicalNot:
+ result = val ? false : true;
+ break;
+ case jinja2::UnaryExpression::UnaryPlus:
+ result = +val;
+ break;
+ case jinja2::UnaryExpression::UnaryMinus:
+ result = -val;
+ break;
+ }
+
+ return result;
+ }
+
+ InternalValue operator() (double val) const
+ {
+ InternalValue result;
+ switch (m_oper)
+ {
+ case jinja2::UnaryExpression::LogicalNot:
+ result = fabs(val) > std::numeric_limits<double>::epsilon() ? false : true;
+ break;
+ case jinja2::UnaryExpression::UnaryPlus:
+ result = +val;
+ break;
+ case jinja2::UnaryExpression::UnaryMinus:
+ result = -val;
+ break;
+ }
+
+ return result;
+ }
+
+ InternalValue operator() (bool val) const
+ {
+ InternalValue result;
+ switch (m_oper)
+ {
+ case jinja2::UnaryExpression::LogicalNot:
+ result = !val;
+ break;
+ default:
+ break;
+ }
+
+ return result;
+ }
+
+ InternalValue operator() (const MapAdapter&) const
+ {
+ InternalValue result;
+ switch (m_oper)
+ {
+ case jinja2::UnaryExpression::LogicalNot:
+ result = true;
+ break;
+ default:
+ break;
+ }
+
+ return result;
+ }
+
+ InternalValue operator() (const ListAdapter&) const
+ {
+ InternalValue result;
+ switch (m_oper)
+ {
+ case jinja2::UnaryExpression::LogicalNot:
+ result = true;
+ break;
+ default:
+ break;
+ }
+
+ return result;
+ }
+
+ template<typename CharT>
+ InternalValue operator() (const std::basic_string<CharT>& val) const
+ {
+ InternalValue result;
+ switch (m_oper)
+ {
+ case jinja2::UnaryExpression::LogicalNot:
+ result = val.empty();
+ break;
+ default:
+ break;
+ }
+
+ return result;
+ }
+
+ template<typename CharT>
+ InternalValue operator() (const std::basic_string_view<CharT>& val) const
+ {
+ InternalValue result;
+ switch (m_oper)
+ {
+ case jinja2::UnaryExpression::LogicalNot:
+ result = val.empty();
+ break;
+ default:
+ break;
+ }
+
+ return result;
+ }
+
+ InternalValue operator() (const EmptyValue&) const
+ {
+ InternalValue result;
+ switch (m_oper)
+ {
+ case jinja2::UnaryExpression::LogicalNot:
+ result = true;
+ break;
+ default:
+ break;
+ }
+
+ return result;
+ }
+
+ UnaryExpression::Operation m_oper;
+};
+
+struct BinaryMathOperation : BaseVisitor<>
+{
+ using BaseVisitor::operator ();
+ using ResultType = InternalValue;
+ // InternalValue operator() (int, int) const {return InternalValue();}
+
+ bool AlmostEqual(double x, double y) const
+ {
+ return std::abs(x - y) <= std::numeric_limits<double>::epsilon() * std::abs(x + y) * 6
+ || std::abs(x - y) < std::numeric_limits<double>::min();
+ }
+
+ BinaryMathOperation(BinaryExpression::Operation oper, BinaryExpression::CompareType compType = BinaryExpression::CaseSensitive)
+ : m_oper(oper)
+ , m_compType(compType)
+ {
+ }
+
+ ResultType operator() (double left, double right) const
+ {
+ ResultType result = 0.0;
+ switch (m_oper)
+ {
+ case jinja2::BinaryExpression::Plus:
+ result = left + right;
+ break;
+ case jinja2::BinaryExpression::Minus:
+ result = left - right;
+ break;
+ case jinja2::BinaryExpression::Mul:
+ result = left * right;
+ break;
+ case jinja2::BinaryExpression::Div:
+ result = left / right;
+ break;
+ case jinja2::BinaryExpression::DivReminder:
+ result = std::remainder(left, right);
+ break;
+ case jinja2::BinaryExpression::DivInteger:
+ {
+ double val = left / right;
+ result = val < 0 ? ceil(val) : floor(val);
+ break;
+ }
+ case jinja2::BinaryExpression::Pow:
+ result = pow(left, right);
+ break;
+ case jinja2::BinaryExpression::LogicalEq:
+ result = AlmostEqual(left, right);
+ break;
+ case jinja2::BinaryExpression::LogicalNe:
+ result = !AlmostEqual(left, right);
+ break;
+ case jinja2::BinaryExpression::LogicalGt:
+ result = left > right;
+ break;
+ case jinja2::BinaryExpression::LogicalLt:
+ result = left < right;
+ break;
+ case jinja2::BinaryExpression::LogicalGe:
+ result = left > right || AlmostEqual(left, right);
+ break;
+ case jinja2::BinaryExpression::LogicalLe:
+ result = left < right || AlmostEqual(left, right);
+ break;
+ default:
+ break;
+ }
+
+ return result;
+ }
+
+ ResultType operator() (int64_t left, int64_t right) const
+ {
+ ResultType result;
+ switch (m_oper)
+ {
+ case jinja2::BinaryExpression::Plus:
+ result = left + right;
+ break;
+ case jinja2::BinaryExpression::Minus:
+ result = left - right;
+ break;
+ case jinja2::BinaryExpression::Mul:
+ result = left * right;
+ break;
+ case jinja2::BinaryExpression::DivInteger:
+ result = left / right;
+ break;
+ case jinja2::BinaryExpression::Div:
+ case jinja2::BinaryExpression::DivReminder:
+ case jinja2::BinaryExpression::Pow:
+ result = this->operator ()(static_cast<double>(left), static_cast<double>(right));
+ break;
+ case jinja2::BinaryExpression::LogicalEq:
+ result = left == right;
+ break;
+ case jinja2::BinaryExpression::LogicalNe:
+ result = left != right;
+ break;
+ case jinja2::BinaryExpression::LogicalGt:
+ result = left > right;
+ break;
+ case jinja2::BinaryExpression::LogicalLt:
+ result = left < right;
+ break;
+ case jinja2::BinaryExpression::LogicalGe:
+ result = left >= right;
+ break;
+ case jinja2::BinaryExpression::LogicalLe:
+ result = left <= right;
+ break;
+ default:
+ break;
+ }
+
+ return result;
+ }
+
+ ResultType operator() (int64_t left, double right) const
+ {
+ return this->operator ()(static_cast<double>(left), static_cast<double>(right));
+ }
+
+ ResultType operator() (double left, int64_t right) const
+ {
+ return this->operator ()(static_cast<double>(left), static_cast<double>(right));
+ }
+
+ template<typename CharT>
+ ResultType operator() (const std::basic_string<CharT> &left, const std::basic_string<CharT> &right) const
+ {
+ return ProcessStrings(std::basic_string_view<CharT>(left), std::basic_string_view<CharT>(right));
+ }
+
+ template<typename CharT1, typename CharT2>
+ std::enable_if_t<!std::is_same<CharT1, CharT2>::value, ResultType> operator() (const std::basic_string<CharT1>& left, const std::basic_string<CharT2>& right) const
+ {
+ auto rightStr = ConvertString<std::basic_string<CharT1>>(right);
+ return ProcessStrings(std::basic_string_view<CharT1>(left), std::basic_string_view<CharT1>(rightStr));
+ }
+
+ template<typename CharT>
+ ResultType operator() (const std::basic_string_view<CharT> &left, const std::basic_string<CharT> &right) const
+ {
+ return ProcessStrings(left, std::basic_string_view<CharT>(right));
+ }
+
+ template<typename CharT1, typename CharT2>
+ std::enable_if_t<!std::is_same<CharT1, CharT2>::value, ResultType> operator() (const std::basic_string_view<CharT1>& left, const std::basic_string<CharT2>& right) const
+ {
+ auto rightStr = ConvertString<std::basic_string<CharT1>>(right);
+ return ProcessStrings(left, std::basic_string_view<CharT1>(rightStr));
+ }
+
+ template<typename CharT>
+ ResultType operator() (const std::basic_string<CharT> &left, const std::basic_string_view<CharT> &right) const
+ {
+ return ProcessStrings(std::basic_string_view<CharT>(left), right);
+ }
+
+ template<typename CharT1, typename CharT2>
+ std::enable_if_t<!std::is_same<CharT1, CharT2>::value, ResultType> operator() (const std::basic_string<CharT1>& left, const std::basic_string_view<CharT2>& right) const
+ {
+ auto rightStr = ConvertString<std::basic_string<CharT1>>(right);
+ return ProcessStrings(std::basic_string_view<CharT1>(left), std::basic_string_view<CharT1>(rightStr));
+ }
+
+ template<typename CharT>
+ ResultType operator() (const std::basic_string_view<CharT> &left, const std::basic_string_view<CharT> &right) const
+ {
+ return ProcessStrings(left, right);
+ }
+
+ template<typename CharT1, typename CharT2>
+ std::enable_if_t<!std::is_same<CharT1, CharT2>::value, ResultType> operator() (const std::basic_string_view<CharT1>& left, const std::basic_string_view<CharT2>& right) const
+ {
+ auto rightStr = ConvertString<std::basic_string<CharT1>>(right);
+ return ProcessStrings(left, std::basic_string_view<CharT1>(rightStr));
+ }
+
+ template<typename CharT>
+ ResultType operator() (const std::basic_string<CharT> &left, int64_t right) const
+ {
+ return RepeatString(std::basic_string_view<CharT>(left), right);
+ }
+
+ template<typename CharT>
+ ResultType operator() (const std::basic_string_view<CharT> &left, int64_t right) const
+ {
+ return RepeatString(left, right);
+ }
+
+ template<typename CharT>
+ ResultType RepeatString(const std::basic_string_view<CharT>& left, const int64_t right) const
+ {
+ using string = std::basic_string<CharT>;
+ ResultType result;
+
+ if(m_oper == jinja2::BinaryExpression::Mul)
+ {
+ string str;
+ for (int i = 0; i < right; ++i)
+ str.append(left.begin(), left.end());
+ result = TargetString(std::move(str));
+ }
+ return result;
+ }
+
+ template<typename CharT>
+ ResultType ProcessStrings(const std::basic_string_view<CharT>& left, const std::basic_string_view<CharT>& right) const
+ {
+ using string = std::basic_string<CharT>;
+ ResultType result;
+
+ switch (m_oper)
+ {
+ case jinja2::BinaryExpression::Plus:
+ {
+ auto str = string(left.begin(), left.end());
+ str.append(right.begin(), right.end());
+ result = TargetString(std::move(str));
+ break;
+ }
+ case jinja2::BinaryExpression::LogicalEq:
+ result = m_compType == BinaryExpression::CaseSensitive ? left == right : boost::iequals(left, right);
+ break;
+ case jinja2::BinaryExpression::LogicalNe:
+ result = m_compType == BinaryExpression::CaseSensitive ? left != right : !boost::iequals(left, right);
+ break;
+ case jinja2::BinaryExpression::LogicalGt:
+ result = m_compType == BinaryExpression::CaseSensitive ? left > right : boost::lexicographical_compare(right, left, boost::algorithm::is_iless());
+ break;
+ case jinja2::BinaryExpression::LogicalLt:
+ result = m_compType == BinaryExpression::CaseSensitive ? left < right : boost::lexicographical_compare(left, right, boost::algorithm::is_iless());
+ break;
+ case jinja2::BinaryExpression::LogicalGe:
+ if (m_compType == BinaryExpression::CaseSensitive)
+ {
+ result = left >= right;
+ }
+ else
+ {
+ result = boost::iequals(left, right) ? true : boost::lexicographical_compare(right, left, boost::algorithm::is_iless());
+ }
+ break;
+ case jinja2::BinaryExpression::LogicalLe:
+ if (m_compType == BinaryExpression::CaseSensitive)
+ {
+ result = left <= right;
+ }
+ else
+ {
+ result = boost::iequals(left, right) ? true : boost::lexicographical_compare(left, right, boost::algorithm::is_iless());
+ }
+ break;
+ default:
+ break;
+ }
+
+ return result;
+ }
+
+ ResultType operator() (const KeyValuePair& left, const KeyValuePair& right) const
+ {
+ ResultType result;
+ switch (m_oper)
+ {
+ case jinja2::BinaryExpression::LogicalEq:
+ result = ConvertToBool(this->operator ()(left.key, right.key)) && ConvertToBool(Apply2<BinaryMathOperation>(left.value, right.value, BinaryExpression::LogicalEq, m_compType));
+ break;
+ case jinja2::BinaryExpression::LogicalNe:
+ result = ConvertToBool(this->operator ()(left.key, right.key)) || ConvertToBool(Apply2<BinaryMathOperation>(left.value, right.value, BinaryExpression::LogicalNe, m_compType));
+ break;
+ default:
+ break;
+ }
+
+ return result;
+ }
+
+ ResultType operator() (const ListAdapter& left, const ListAdapter& right) const
+ {
+ ResultType result;
+ if (m_oper == jinja2::BinaryExpression::Plus)
+ {
+ InternalValueList values;
+ values.reserve(left.GetSize().value_or(0) + right.GetSize().value_or(0));
+ for (auto& v : left)
+ values.push_back(v);
+ for (auto& v : right)
+ values.push_back(v);
+ result = ListAdapter::CreateAdapter(std::move(values));
+ }
+
+ return result;
+ }
+
+ ResultType operator() (const ListAdapter& left, int64_t right) const
+ {
+ ResultType result;
+ if (right >= 0 && m_oper == jinja2::BinaryExpression::Mul)
+ {
+ InternalValueList values;
+ values.reserve(left.GetSize().value_or(0));
+ for (auto& v : left)
+ values.push_back(v);
+ auto listSize = values.size() * right;
+ result = ListAdapter::CreateAdapter(static_cast<size_t>(listSize),
+ [size = values.size(), values = std::move(values)](size_t idx) { return values[idx % size]; });
+ }
+
+ return result;
+ }
+
+ ResultType operator() (bool left, bool right) const
+ {
+ ResultType result;
+ switch (m_oper)
+ {
+ case jinja2::BinaryExpression::LogicalEq:
+ result = left == right;
+ break;
+ case jinja2::BinaryExpression::LogicalNe:
+ result = left != right;
+ break;
+ case jinja2::BinaryExpression::LogicalLt:
+ result = (left ? 1 : 0) < (right ? 1 : 0);
+ break;
+ default:
+ break;
+ }
+
+ return result;
+ }
+
+ ResultType operator() (EmptyValue, EmptyValue) const
+ {
+ ResultType result;
+ switch (m_oper)
+ {
+ case jinja2::BinaryExpression::LogicalEq:
+ result = true;
+ break;
+ case jinja2::BinaryExpression::LogicalNe:
+ result = false;
+ break;
+ default:
+ break;
+ }
+
+ return result;
+ }
+
+ template<typename T>
+ ResultType operator() (EmptyValue, T&&) const
+ {
+ ResultType result;
+ switch (m_oper)
+ {
+ case jinja2::BinaryExpression::LogicalEq:
+ result = false;
+ break;
+ case jinja2::BinaryExpression::LogicalNe:
+ result = true;
+ break;
+ default:
+ break;
+ }
+
+ return result;
+ }
+
+ template<typename T>
+ ResultType operator() (T&&, EmptyValue) const
+ {
+ ResultType result;
+ switch (m_oper)
+ {
+ case jinja2::BinaryExpression::LogicalEq:
+ result = false;
+ break;
+ case jinja2::BinaryExpression::LogicalNe:
+ result = true;
+ break;
+ default:
+ break;
+ }
+
+ return result;
+ }
+
+ BinaryExpression::Operation m_oper;
+ BinaryExpression::CompareType m_compType;
+};
+
+struct BooleanEvaluator : BaseVisitor<bool>
+{
+ using BaseVisitor::operator ();
+
+ bool operator() (int64_t val) const
+ {
+ return val != 0;
+ }
+
+ bool operator() (double val) const
+ {
+ return fabs(val) < std::numeric_limits<double>::epsilon();
+ }
+
+ bool operator() (bool val) const
+ {
+ return val;
+ }
+
+ template<typename CharT>
+ bool operator()(const std::basic_string<CharT>& str) const
+ {
+ return !str.empty();
+ }
+
+ template<typename CharT>
+ bool operator()(const std::basic_string_view<CharT>& str) const
+ {
+ return !str.empty();
+ }
+
+ bool operator() (const MapAdapter& val) const
+ {
+ return val.GetSize() != 0ULL;
+ }
+
+ bool operator() (const ListAdapter& val) const
+ {
+ return val.GetSize() != 0ULL;
+ }
+
+ bool operator() (const EmptyValue&) const
+ {
+ return false;
+ }
+};
+
+template<typename TargetType>
+struct NumberEvaluator
+{
+ NumberEvaluator(TargetType def = 0) : m_def(def)
+ {}
+
+ TargetType operator ()(int64_t val) const
+ {
+ return static_cast<TargetType>(val);
+ }
+ TargetType operator ()(double val) const
+ {
+ return static_cast<TargetType>(val);
+ }
+ TargetType operator ()(bool val) const
+ {
+ return static_cast<TargetType>(val);
+ }
+ template<typename U>
+ TargetType operator()(U&&) const
+ {
+ return m_def;
+ }
+
+ TargetType m_def;
+};
+
+using IntegerEvaluator = NumberEvaluator<int64_t>;
+using DoubleEvaluator = NumberEvaluator<double>;
+
+
+struct StringJoiner : BaseVisitor<TargetString>
+{
+ using BaseVisitor::operator ();
+
+ template<typename CharT>
+ TargetString operator() (EmptyValue, const std::basic_string<CharT>& str) const
+ {
+ return str;
+ }
+
+ template<typename CharT>
+ TargetString operator() (EmptyValue, const std::basic_string_view<CharT>& str) const
+ {
+ return std::basic_string<CharT>(str.begin(), str.end());
+ }
+
+ template<typename CharT>
+ TargetString operator() (const std::basic_string<CharT>& left, const std::basic_string<CharT>& right) const
+ {
+ return left + right;
+ }
+
+ template<typename CharT1, typename CharT2>
+ std::enable_if_t<!std::is_same<CharT1, CharT2>::value, TargetString> operator() (const std::basic_string<CharT1>& left, const std::basic_string<CharT2>& right) const
+ {
+ return left + ConvertString<std::basic_string<CharT1>>(right);
+ }
+
+ template<typename CharT>
+ TargetString operator() (std::basic_string<CharT> left, const std::basic_string_view<CharT>& right) const
+ {
+ left.append(right.begin(), right.end());
+ return std::move(left);
+ }
+
+ template<typename CharT1, typename CharT2>
+ std::enable_if_t<!std::is_same<CharT1, CharT2>::value, TargetString> operator() (std::basic_string<CharT1> left, const std::basic_string_view<CharT2>& right) const
+ {
+ auto r = ConvertString<std::basic_string<CharT1>>(right);
+ left.append(r.begin(), r.end());
+ return std::move(left);
+ }
+};
+
+template<typename Fn>
+struct StringConverterImpl : public BaseVisitor<decltype(std::declval<Fn>()(std::declval<std::string_view>()))>
+{
+ using R = decltype(std::declval<Fn>()(std::string_view()));
+ using BaseVisitor<R>::operator ();
+
+ StringConverterImpl(const Fn& fn) : m_fn(fn) {}
+
+ template<typename CharT>
+ R operator()(const std::basic_string<CharT>& str) const
+ {
+ return m_fn(std::basic_string_view<CharT>(str));
+ }
+
+ template<typename CharT>
+ R operator()(const std::basic_string_view<CharT>& str) const
+ {
+ return m_fn(str);
+ }
+
+ const Fn& m_fn;
+};
+
+template<typename CharT>
+struct SameStringGetter : public visitors::BaseVisitor<nonstd::expected<void, std::basic_string<CharT>>>
+{
+ using ResultString = std::basic_string<CharT>;
+ using OtherString = std::conditional_t<std::is_same<CharT, char>::value, std::wstring, std::string>;
+ using ResultStringView = std::basic_string_view<CharT>;
+ using OtherStringView = std::conditional_t<std::is_same<CharT, char>::value, std::wstring_view, std::string_view>;
+ using Result = nonstd::expected<void, ResultString>;
+ using BaseVisitor<Result>::operator ();
+
+ Result operator()(const ResultString& str) const
+ {
+ return nonstd::make_unexpected(str);
+ }
+
+ Result operator()(const ResultStringView& str) const
+ {
+ return nonstd::make_unexpected(ResultString(str.begin(), str.end()));
+ }
+
+ Result operator()(const OtherString& str) const
+ {
+ return nonstd::make_unexpected(ConvertString<ResultString>(str));
+ }
+
+ Result operator()(const OtherStringView& str) const
+ {
+ return nonstd::make_unexpected(ConvertString<ResultString>(str));
+ }
+};
+
+} // namespace visitors
+
+inline bool ConvertToBool(const InternalValue& val)
+{
+ return Apply<visitors::BooleanEvaluator>(val);
+}
+
+inline int64_t ConvertToInt(const InternalValue& val, int64_t def = 0)
+{
+ return Apply<visitors::IntegerEvaluator>(val, def);
+}
+
+inline double ConvertToDouble(const InternalValue& val, double def = 0)
+{
+ return Apply<visitors::DoubleEvaluator>(val, def);
+}
+
+template<template<typename> class Cvt = visitors::StringConverterImpl, typename Fn>
+auto ApplyStringConverter(const InternalValue& str, Fn&& fn)
+{
+ return Apply<Cvt<Fn>>(str, std::forward<Fn>(fn));
+}
+
+template<typename CharT>
+auto GetAsSameString(const std::basic_string<CharT>&, const InternalValue& val)
+{
+ using Result = std::optional<std::basic_string<CharT>>;
+ auto result = Apply<visitors::SameStringGetter<CharT>>(val);
+ if (!result)
+ return Result(result.error());
+
+ return Result();
+}
+
+template<typename CharT>
+auto GetAsSameString(const std::basic_string_view<CharT>&, const InternalValue& val)
+{
+ using Result = std::optional<std::basic_string<CharT>>;
+ auto result = Apply<visitors::SameStringGetter<CharT>>(val);
+ if (!result)
+ return Result(result.error());
+
+ return Result();
+}
+
+inline bool operator==(const InternalValueData& lhs, const InternalValueData& rhs)
+{
+ InternalValue cmpRes;
+ cmpRes = Apply2<visitors::BinaryMathOperation>(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
diff --git a/contrib/libs/jinja2cpp/ya.make b/contrib/libs/jinja2cpp/ya.make
new file mode 100644
index 0000000000..fff6152672
--- /dev/null
+++ b/contrib/libs/jinja2cpp/ya.make
@@ -0,0 +1,68 @@
+# Generated by devtools/yamaker from nixpkgs 22.11.
+
+LIBRARY()
+
+LICENSE(
+ MIT AND
+ MPL-1.1 AND
+ MPL-2.0
+)
+
+LICENSE_TEXTS(.yandex_meta/licenses.list.txt)
+
+VERSION(1.3.1)
+
+ORIGINAL_SOURCE(https://github.com/jinja2cpp/Jinja2Cpp/archive/1.3.1.tar.gz)
+
+PEERDIR(
+ contrib/libs/fmt
+ contrib/libs/rapidjson
+ contrib/restricted/boost/algorithm
+ contrib/restricted/boost/container
+ contrib/restricted/boost/filesystem
+ contrib/restricted/boost/numeric_conversion
+ contrib/restricted/boost/unordered
+ contrib/restricted/boost/variant
+ contrib/restricted/expected-lite
+)
+
+ADDINCL(
+ GLOBAL contrib/libs/jinja2cpp/include
+ GLOBAL contrib/libs/rapidjson/include
+)
+
+NO_COMPILER_WARNINGS()
+
+NO_UTIL()
+
+CFLAGS(
+ -DBOOST_ERROR_CODE_HEADER_ONLY
+ -DBOOST_SYSTEM_NO_DEPRECATED
+ -DFMT_HEADER_ONLY=1
+ -DFMT_USE_INTERNAL=TRUE
+ -DJINJA2CPP_BUILD_AS_SHARED
+ -DJINJA2CPP_LINK_AS_SHARED
+ -DJINJA2CPP_USE_REGEX_BOOST
+)
+
+SRCS(
+ src/binding/rapid_json_serializer.cpp
+ src/error_info.cpp
+ src/expression_evaluator.cpp
+ src/expression_parser.cpp
+ src/filesystem_handler.cpp
+ src/filters.cpp
+ src/generic_list.cpp
+ src/internal_value.cpp
+ src/lexer.cpp
+ src/serialize_filters.cpp
+ src/statements.cpp
+ src/string_converter_filter.cpp
+ src/template.cpp
+ src/template_env.cpp
+ src/template_parser.cpp
+ src/testers.cpp
+ src/value.cpp
+)
+
+END()